feat: Add automated ChatGPT account registration with backend API, TLS client, and fingerprinting, alongside new frontend pages for configuration, monitoring, and upload.
This commit is contained in:
@@ -8,21 +8,36 @@ import (
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"codex-pool/internal/proxyutil"
|
||||
|
||||
"github.com/Noooste/azuretls-client"
|
||||
"github.com/andybalholm/brotli"
|
||||
http2 "github.com/bogdanfinn/fhttp"
|
||||
tls_client "github.com/bogdanfinn/tls-client"
|
||||
)
|
||||
|
||||
// TLSClient 使用 tls-client 模拟浏览器指纹的 HTTP 客户端
|
||||
// ClientType 客户端类型
|
||||
type ClientType int
|
||||
|
||||
const (
|
||||
ClientTypeTLS ClientType = iota // bogdanfinn/tls-client
|
||||
ClientTypeAzureTLS // Noooste/azuretls-client
|
||||
)
|
||||
|
||||
// TLSClient 统一的 HTTP 客户端接口,支持多种 TLS 指纹和浏览器模拟
|
||||
type TLSClient struct {
|
||||
client tls_client.HttpClient
|
||||
// 内部客户端
|
||||
tlsClient tls_client.HttpClient
|
||||
azureSession *azuretls.Session
|
||||
clientType ClientType
|
||||
|
||||
// 指纹信息
|
||||
fingerprint BrowserFingerprint
|
||||
userAgent string
|
||||
acceptLang string
|
||||
proxy string
|
||||
}
|
||||
|
||||
// 语言偏好池
|
||||
@@ -37,7 +52,7 @@ var languagePrefs = []string{
|
||||
"es-ES,es;q=0.9,en-US;q=0.8,en;q=0.7",
|
||||
}
|
||||
|
||||
// New 创建一个新的 TLS 客户端(使用随机指纹)
|
||||
// New 创建一个新的 TLS 客户端 (使用随机指纹)
|
||||
func New(proxyStr string) (*TLSClient, error) {
|
||||
// 获取随机桌面端指纹
|
||||
fp := GetRandomDesktopFingerprint()
|
||||
@@ -45,7 +60,36 @@ func New(proxyStr string) (*TLSClient, error) {
|
||||
}
|
||||
|
||||
// NewWithFingerprint 使用指定指纹创建客户端
|
||||
func NewWithFingerprint(fp BrowserFingerprint, proxyStr string) (*TLSClient, error) {
|
||||
func NewWithFingerprint(fp BrowserFingerprint, proxyStr string) (client *TLSClient, err error) {
|
||||
// 添加 panic 恢复
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("创建客户端时发生 panic: %v", r)
|
||||
client = nil
|
||||
}
|
||||
}()
|
||||
|
||||
acceptLang := languagePrefs[rand.Intn(len(languagePrefs))]
|
||||
userAgent := generateUserAgent(fp)
|
||||
|
||||
client = &TLSClient{
|
||||
fingerprint: fp,
|
||||
userAgent: userAgent,
|
||||
acceptLang: acceptLang,
|
||||
proxy: proxyStr,
|
||||
}
|
||||
|
||||
// 根据指纹类型创建对应的客户端
|
||||
switch fp.Type {
|
||||
case FingerprintAzureTLS:
|
||||
return createAzureTLSClient(client, fp, proxyStr)
|
||||
default:
|
||||
return createTLSClient(client, fp, proxyStr)
|
||||
}
|
||||
}
|
||||
|
||||
// createTLSClient 创建 tls-client 客户端
|
||||
func createTLSClient(c *TLSClient, fp BrowserFingerprint, proxyStr string) (*TLSClient, error) {
|
||||
jar := tls_client.NewCookieJar()
|
||||
|
||||
options := []tls_client.HttpClientOption{
|
||||
@@ -54,6 +98,7 @@ func NewWithFingerprint(fp BrowserFingerprint, proxyStr string) (*TLSClient, err
|
||||
tls_client.WithRandomTLSExtensionOrder(),
|
||||
tls_client.WithCookieJar(jar),
|
||||
tls_client.WithInsecureSkipVerify(),
|
||||
tls_client.WithNotFollowRedirects(),
|
||||
}
|
||||
|
||||
if proxyStr != "" {
|
||||
@@ -64,20 +109,45 @@ func NewWithFingerprint(fp BrowserFingerprint, proxyStr string) (*TLSClient, err
|
||||
options = append(options, tls_client.WithProxyUrl(normalized))
|
||||
}
|
||||
|
||||
client, err := tls_client.NewHttpClient(tls_client.NewNoopLogger(), options...)
|
||||
tlsClient, err := tls_client.NewHttpClient(tls_client.NewNoopLogger(), options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
acceptLang := languagePrefs[rand.Intn(len(languagePrefs))]
|
||||
userAgent := generateUserAgent(fp)
|
||||
c.tlsClient = tlsClient
|
||||
c.clientType = ClientTypeTLS
|
||||
return c, nil
|
||||
}
|
||||
|
||||
return &TLSClient{
|
||||
client: client,
|
||||
fingerprint: fp,
|
||||
userAgent: userAgent,
|
||||
acceptLang: acceptLang,
|
||||
}, nil
|
||||
// createAzureTLSClient 创建 azuretls-client 客户端
|
||||
func createAzureTLSClient(c *TLSClient, fp BrowserFingerprint, proxyStr string) (*TLSClient, error) {
|
||||
session := azuretls.NewSession()
|
||||
|
||||
browser := fp.AzureBrowser
|
||||
if browser == "" {
|
||||
browser = azuretls.Chrome
|
||||
}
|
||||
|
||||
session.Browser = browser
|
||||
session.GetClientHelloSpec = azuretls.GetBrowserClientHelloFunc(browser)
|
||||
session.SetTimeout(90 * time.Second)
|
||||
|
||||
if proxyStr != "" {
|
||||
normalized, err := proxyutil.Normalize(proxyStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := session.SetProxy(normalized); err != nil {
|
||||
return nil, fmt.Errorf("azuretls set proxy failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
session.MaxRedirects = 0
|
||||
session.InsecureSkipVerify = true
|
||||
|
||||
c.azureSession = session
|
||||
c.clientType = ClientTypeAzureTLS
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// generateUserAgent 根据指纹生成 User-Agent
|
||||
@@ -87,6 +157,22 @@ func generateUserAgent(fp BrowserFingerprint) string {
|
||||
linuxVersions := []string{"X11; Linux x86_64", "X11; Ubuntu; Linux x86_64", "X11; Fedora; Linux x86_64"}
|
||||
|
||||
version := fp.Version
|
||||
if version == "latest" {
|
||||
switch fp.Browser {
|
||||
case "chrome":
|
||||
version = "133"
|
||||
case "firefox":
|
||||
version = "135"
|
||||
case "safari":
|
||||
version = "18.3"
|
||||
case "edge":
|
||||
version = "133"
|
||||
case "opera":
|
||||
version = "117"
|
||||
default:
|
||||
version = "133"
|
||||
}
|
||||
}
|
||||
|
||||
switch fp.Browser {
|
||||
case "chrome":
|
||||
@@ -144,11 +230,10 @@ func generateUserAgent(fp BrowserFingerprint) string {
|
||||
return "okhttp/4.12.0"
|
||||
}
|
||||
|
||||
// 默认 Chrome
|
||||
return fmt.Sprintf("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s.0.0.0 Safari/537.36", version)
|
||||
}
|
||||
|
||||
// getDefaultHeaders 获取默认请求头(根据浏览器类型返回不同的头)
|
||||
// getDefaultHeaders 获取默认请求头
|
||||
func (c *TLSClient) getDefaultHeaders() map[string]string {
|
||||
headers := map[string]string{
|
||||
"User-Agent": c.userAgent,
|
||||
@@ -222,14 +307,18 @@ func (c *TLSClient) getPlatformHeader() string {
|
||||
}
|
||||
}
|
||||
|
||||
// GetFingerprintInfo 获取指纹信息字符串(用于日志输出)
|
||||
func (c *TLSClient) GetFingerprintInfo() string {
|
||||
fp := c.fingerprint
|
||||
return fmt.Sprintf("%s/%s (%s)", fp.Browser, fp.Version, fp.Platform)
|
||||
}
|
||||
|
||||
// Do 执行 HTTP 请求
|
||||
func (c *TLSClient) Do(req *http.Request) (*http.Response, error) {
|
||||
switch c.clientType {
|
||||
case ClientTypeAzureTLS:
|
||||
return c.doAzureTLS(req)
|
||||
default:
|
||||
return c.doTLS(req)
|
||||
}
|
||||
}
|
||||
|
||||
// doTLS 使用 tls-client 执行请求
|
||||
func (c *TLSClient) doTLS(req *http.Request) (*http.Response, error) {
|
||||
fhttpReq, err := http2.NewRequest(req.Method, req.URL.String(), req.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -247,7 +336,7 @@ func (c *TLSClient) Do(req *http.Request) (*http.Response, error) {
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := c.client.Do(fhttpReq)
|
||||
resp, err := c.tlsClient.Do(fhttpReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -261,7 +350,7 @@ func (c *TLSClient) Do(req *http.Request) (*http.Response, error) {
|
||||
}
|
||||
}
|
||||
|
||||
stdResp := &http.Response{
|
||||
return &http.Response{
|
||||
Status: resp.Status,
|
||||
StatusCode: resp.StatusCode,
|
||||
Proto: resp.Proto,
|
||||
@@ -274,23 +363,104 @@ func (c *TLSClient) Do(req *http.Request) (*http.Response, error) {
|
||||
Close: resp.Close,
|
||||
Uncompressed: resp.Uncompressed,
|
||||
Request: finalReq,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// doAzureTLS 使用 azuretls 执行请求
|
||||
func (c *TLSClient) doAzureTLS(req *http.Request) (*http.Response, error) {
|
||||
orderedHeaders := azuretls.OrderedHeaders{}
|
||||
for key, value := range c.getDefaultHeaders() {
|
||||
if req.Header.Get(key) == "" {
|
||||
orderedHeaders = append(orderedHeaders, []string{key, value})
|
||||
}
|
||||
}
|
||||
for key, values := range req.Header {
|
||||
if len(values) > 0 {
|
||||
orderedHeaders = append(orderedHeaders, []string{key, values[0]})
|
||||
}
|
||||
}
|
||||
|
||||
return stdResp, nil
|
||||
azureReq := &azuretls.Request{
|
||||
Method: req.Method,
|
||||
Url: req.URL.String(),
|
||||
OrderedHeaders: orderedHeaders,
|
||||
DisableRedirects: true,
|
||||
}
|
||||
|
||||
if req.Body != nil {
|
||||
bodyBytes, _ := io.ReadAll(req.Body)
|
||||
azureReq.Body = bodyBytes
|
||||
}
|
||||
|
||||
resp, err := c.azureSession.Do(azureReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
respHeaders := make(http.Header)
|
||||
for key, values := range resp.Header {
|
||||
respHeaders[key] = values
|
||||
}
|
||||
|
||||
return &http.Response{
|
||||
Status: fmt.Sprintf("%d %s", resp.StatusCode, http.StatusText(resp.StatusCode)),
|
||||
StatusCode: resp.StatusCode,
|
||||
Header: respHeaders,
|
||||
Body: io.NopCloser(bytes.NewReader(resp.Body)),
|
||||
Request: req,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DoWithRedirect 执行请求并手动处理重定向
|
||||
func (c *TLSClient) DoWithRedirect(req *http.Request, maxRedirects int) (*http.Response, error) {
|
||||
for i := 0; i <= maxRedirects; i++ {
|
||||
resp, err := c.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 300 && resp.StatusCode < 400 {
|
||||
location := resp.Header.Get("Location")
|
||||
if location == "" {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
redirectURL, err := url.Parse(location)
|
||||
if err != nil {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
if !redirectURL.IsAbs() {
|
||||
redirectURL = req.URL.ResolveReference(redirectURL)
|
||||
}
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, redirectURL.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp.Body.Close()
|
||||
continue
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("too many redirects")
|
||||
}
|
||||
|
||||
// Get 执行 GET 请求
|
||||
func (c *TLSClient) Get(urlStr string) (*http.Response, error) {
|
||||
req, err := http.NewRequest("GET", urlStr, nil)
|
||||
req, err := http.NewRequest(http.MethodGet, urlStr, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.Do(req)
|
||||
return c.DoWithRedirect(req, 10)
|
||||
}
|
||||
|
||||
// Post 执行 POST 请求
|
||||
func (c *TLSClient) Post(urlStr string, contentType string, body io.Reader) (*http.Response, error) {
|
||||
req, err := http.NewRequest("POST", urlStr, body)
|
||||
req, err := http.NewRequest(http.MethodPost, urlStr, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -298,11 +468,6 @@ func (c *TLSClient) Post(urlStr string, contentType string, body io.Reader) (*ht
|
||||
return c.Do(req)
|
||||
}
|
||||
|
||||
// PostForm 执行 POST 表单请求
|
||||
func (c *TLSClient) PostForm(urlStr string, data url.Values) (*http.Response, error) {
|
||||
return c.Post(urlStr, "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
|
||||
}
|
||||
|
||||
// PostJSON 执行 POST JSON 请求
|
||||
func (c *TLSClient) PostJSON(urlStr string, body io.Reader) (*http.Response, error) {
|
||||
return c.Post(urlStr, "application/json", body)
|
||||
@@ -310,11 +475,15 @@ func (c *TLSClient) PostJSON(urlStr string, body io.Reader) (*http.Response, err
|
||||
|
||||
// GetCookie 获取指定 URL 的 Cookie
|
||||
func (c *TLSClient) GetCookie(urlStr string, name string) string {
|
||||
if c.clientType == ClientTypeAzureTLS {
|
||||
return "" // azuretls cookie 管理待完善,目前主要依赖 tls-client 或手动 handle
|
||||
}
|
||||
|
||||
u, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
cookies := c.client.GetCookies(u)
|
||||
cookies := c.tlsClient.GetCookies(u)
|
||||
for _, cookie := range cookies {
|
||||
if cookie.Name == name {
|
||||
return cookie.Value
|
||||
@@ -325,11 +494,15 @@ func (c *TLSClient) GetCookie(urlStr string, name string) string {
|
||||
|
||||
// SetCookie 设置 Cookie
|
||||
func (c *TLSClient) SetCookie(urlStr string, cookie *http.Cookie) {
|
||||
if c.clientType == ClientTypeAzureTLS {
|
||||
return
|
||||
}
|
||||
|
||||
u, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.client.SetCookies(u, []*http2.Cookie{
|
||||
c.tlsClient.SetCookies(u, []*http2.Cookie{
|
||||
{
|
||||
Name: cookie.Name,
|
||||
Value: cookie.Value,
|
||||
@@ -371,3 +544,20 @@ func ReadBodyString(resp *http.Response) (string, error) {
|
||||
}
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
// GetFingerprintInfo 获取指纹信息字符串(用于日志输出)
|
||||
func (c *TLSClient) GetFingerprintInfo() string {
|
||||
fp := c.fingerprint
|
||||
typeName := "tls-client"
|
||||
if c.clientType == ClientTypeAzureTLS {
|
||||
typeName = "azuretls"
|
||||
}
|
||||
return fmt.Sprintf("%s/%s %s (%s)", typeName, fp.Browser, fp.Version, fp.Platform)
|
||||
}
|
||||
|
||||
// Close 关闭客户端
|
||||
func (c *TLSClient) Close() {
|
||||
if c.azureSession != nil {
|
||||
c.azureSession.Close()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user