From 91c503b5c5a187a28518b99cc51c465264d00ecf Mon Sep 17 00:00:00 2001 From: nite Date: Mon, 26 Jan 2026 03:42:39 +0800 Subject: [PATCH] u --- flaresolverr.go | 125 ++++++++++++++++++++++++++++++++++++++ go.mod | 24 +++++++- go.sum | 46 ++++++++++++++ test/flaresolverr_test.go | 27 +++++--- 4 files changed, 212 insertions(+), 10 deletions(-) diff --git a/flaresolverr.go b/flaresolverr.go index a20688c..25d1803 100644 --- a/flaresolverr.go +++ b/flaresolverr.go @@ -4,8 +4,13 @@ import ( "encoding/json" "fmt" "net/http" + "regexp" "strings" "time" + + fhttp "github.com/bogdanfinn/fhttp" + tls_client "github.com/bogdanfinn/tls-client" + "github.com/bogdanfinn/tls-client/profiles" ) var instances map[string]*Flaresolverr = make(map[string]*Flaresolverr) @@ -15,6 +20,8 @@ type Flaresolverr struct { v1Url string sessionID string proxy string + userAgent string + cookies []*http.Cookie } func GetInstance(flaresolverrURL string, sessionID string, proxy string) (*Flaresolverr, error) { @@ -87,6 +94,9 @@ func (f *Flaresolverr) GetV1(reqURL string, req *V1RequestBase) (*V1ResponseBase req.Cmd = "request.get" req.URL = reqURL req.Session = f.sessionID + if f.proxy != "" { + req.Proxy.Url = f.proxy + } return f.requestV1(req) } @@ -100,6 +110,9 @@ func (f *Flaresolverr) PostV1(reqURL string, req *V1RequestBase) (*V1ResponseBas req.Cmd = "request.post" req.URL = reqURL req.Session = f.sessionID + if f.proxy != "" { + req.Proxy.Url = f.proxy + } return f.requestV1(req) } @@ -109,6 +122,9 @@ func (f *Flaresolverr) CreateSessionV1(sessionID string) (*V1ResponseBase, error Session: sessionID, Cmd: "sessions.create", } + if f.proxy != "" { + req.Proxy.Url = f.proxy + } return f.requestV1(req) } @@ -127,3 +143,112 @@ func (f *Flaresolverr) ListSessionsV1(sessionID string) (*V1ResponseBase, error) } return f.requestV1(req) } + +func (f *Flaresolverr) DetermineProfile(userAgent string) profiles.ClientProfile { + re := regexp.MustCompile(`Chrome/(\d+)\.`) + matches := re.FindStringSubmatch(userAgent) + + if len(matches) < 2 { + return profiles.Chrome_120 + } + + ver := matches[1] + + switch ver { + case "124", "123", "122", "121", "120": + return profiles.Chrome_120 + case "119", "118", "117": + return profiles.Chrome_117 + default: + return profiles.Chrome_120 + } +} + +func (f *Flaresolverr) SimulateGet(url string, ua string, cookies []*http.Cookie, opts ...tls_client.HttpClientOption) (*fhttp.Response, error) { + currentUA := ua + currentCookies := cookies + + // Use cached credentials if arguments are empty + if currentUA == "" { + currentUA = f.userAgent + } + if len(currentCookies) == 0 { + currentCookies = f.cookies + } + + // Helper to perform the request + doRequest := func(ua string, cookies []*http.Cookie) (*fhttp.Response, error) { + profile := f.DetermineProfile(ua) + reqOpts := append([]tls_client.HttpClientOption{}, opts...) // Clone opts + reqOpts = append(reqOpts, tls_client.WithClientProfile(profile)) + if f.proxy != "" { + reqOpts = append(reqOpts, tls_client.WithProxyUrl(f.proxy)) + } + + client, err := tls_client.NewHttpClient(tls_client.NewNoopLogger(), reqOpts...) + if err != nil { + return nil, fmt.Errorf("failed to create client: %w", err) + } + req, err := fhttp.NewRequest(http.MethodGet, url, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + req.Header.Set("User-Agent", ua) + for _, cookie := range cookies { + req.AddCookie(&fhttp.Cookie{ + Name: cookie.Name, + Value: cookie.Value, + Path: cookie.Path, + Domain: cookie.Domain, + Expires: cookie.Expires, + RawExpires: cookie.RawExpires, + MaxAge: cookie.MaxAge, + Secure: cookie.Secure, + HttpOnly: cookie.HttpOnly, + SameSite: fhttp.SameSite(cookie.SameSite), + Raw: cookie.Raw, + Unparsed: cookie.Unparsed, + }) + } + return client.Do(req) + } + + // First attempt + resp, err := doRequest(currentUA, currentCookies) + + // Check for retry condition (err or 403) + needRetry := false + if err == nil && (resp.StatusCode == 403 || resp.StatusCode == 503) { + needRetry = true + } + + if needRetry || (currentUA == "" && len(currentCookies) == 0) { + // Refresh credentials + fmt.Println("[Flaresolverr] Refreshing credentials via GetV1...") + v1Resp, err := f.GetV1(url, nil) + if err != nil { + return nil, fmt.Errorf("failed to refresh credentials: %w", err) + } + if v1Resp.Solution.Status != 200 { + return nil, fmt.Errorf("refresh failed with status: %d", v1Resp.Solution.Status) + } + + // Update state + currentUA = v1Resp.Solution.UserAgent + currentCookies = v1Resp.Solution.Cookies + f.userAgent = currentUA + f.cookies = currentCookies + + // Retry request + resp, err = doRequest(currentUA, currentCookies) + if err != nil { + return nil, fmt.Errorf("retry failed: %w", err) + } + } + + if err != nil { + return nil, err + } + + return resp, nil +} diff --git a/go.mod b/go.mod index 7340c75..a567ce6 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,25 @@ module git.nite07.com/nite/go-flaresolverr -go 1.24.0 +go 1.24.1 -require github.com/go-resty/resty/v2 v2.17.1 +require ( + github.com/bogdanfinn/fhttp v0.6.7 + github.com/bogdanfinn/tls-client v1.13.2-0.20260121072726-145b2fdcf1c7 + github.com/go-resty/resty/v2 v2.17.1 +) -require golang.org/x/net v0.49.0 // indirect +require ( + github.com/andybalholm/brotli v1.2.0 // indirect + github.com/bdandy/go-errors v1.2.2 // indirect + github.com/bdandy/go-socks4 v1.2.3 // indirect + github.com/bogdanfinn/quic-go-utls v1.0.7-utls // indirect + github.com/bogdanfinn/utls v1.7.7-barnius // indirect + github.com/bogdanfinn/websocket v1.5.4-barnius // indirect + github.com/klauspost/compress v1.18.2 // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.33.0 // indirect +) diff --git a/go.sum b/go.sum index 6044c61..987e5a1 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,52 @@ +github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/bdandy/go-errors v1.2.2 h1:WdFv/oukjTJCLa79UfkGmwX7ZxONAihKu4V0mLIs11Q= +github.com/bdandy/go-errors v1.2.2/go.mod h1:NkYHl4Fey9oRRdbB1CoC6e84tuqQHiqrOcZpqFEkBxM= +github.com/bdandy/go-socks4 v1.2.3 h1:Q6Y2heY1GRjCtHbmlKfnwrKVU/k81LS8mRGLRlmDlic= +github.com/bdandy/go-socks4 v1.2.3/go.mod h1:98kiVFgpdogR8aIGLWLvjDVZ8XcKPsSI/ypGrO+bqHI= +github.com/bogdanfinn/fhttp v0.6.7 h1:yTDywa9INbRqePBE5gHhpxlMjvAQ0bdX77pvOTPJoPI= +github.com/bogdanfinn/fhttp v0.6.7/go.mod h1:A+EKDzMx2hb4IUbMx4TlkoHnaJEiLl8r/1Ss1Y+5e5M= +github.com/bogdanfinn/quic-go-utls v1.0.7-utls h1:opxU/wt2C6FcD3rkGSOwfpQgfGSFx9eAKYQrFwYBzuo= +github.com/bogdanfinn/quic-go-utls v1.0.7-utls/go.mod h1:bk8QMY2KypO8A6LzHJ7C4+bdB0ksLOd6NZt600wXYe8= +github.com/bogdanfinn/tls-client v1.13.2-0.20260121072726-145b2fdcf1c7 h1:7/7xbSyvJTwxjOP5iBw5CM3SmIBGqHwC5nFkbkvdI28= +github.com/bogdanfinn/tls-client v1.13.2-0.20260121072726-145b2fdcf1c7/go.mod h1:z01rwJcKsjx8Vo0dXKwz7YRkqo+XNIKC0pJaCdJDHgs= +github.com/bogdanfinn/utls v1.7.7-barnius h1:OuJ497cc7F3yKNVHRsYPQdGggmk5x6+V5ZlrCR7fOLU= +github.com/bogdanfinn/utls v1.7.7-barnius/go.mod h1:aAK1VZQlpKZClF1WEQeq6kyclbkPq4hz6xTbB5xSlmg= +github.com/bogdanfinn/websocket v1.5.4-barnius h1:qn3DU/KMHMNPNnwDCtA/IN3QqmzV98DVsvQkQkxruKw= +github.com/bogdanfinn/websocket v1.5.4-barnius/go.mod h1:IWHoWLZd+5/o9340A/m/ApAOBmWDSTUXuL7zSVmRHQk= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-resty/resty/v2 v2.17.1 h1:x3aMpHK1YM9e4va/TMDRlusDDoZiQ+ViDu/WpA6xTM4= github.com/go-resty/resty/v2 v2.17.1/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA= +github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= +github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 h1:YqAladjX7xpA6BM04leXMWAEjS0mTZ5kUU9KRBriQJc= +github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5/go.mod h1:2JjD2zLQYH5HO74y5+aE3remJQvl6q4Sn6aWA2wD1Ng= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= +go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/net v0.0.0-20211104170005-ce137452f963/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/test/flaresolverr_test.go b/test/flaresolverr_test.go index 5e3e553..2130a30 100644 --- a/test/flaresolverr_test.go +++ b/test/flaresolverr_test.go @@ -1,27 +1,40 @@ package test import ( - "encoding/json" + "io" "testing" "git.nite07.com/nite/go-flaresolverr" ) func TestGetV1(t *testing.T) { - f, err := flaresolverr.GetInstance("http://100.64.0.1:8191", "", "") + f, err := flaresolverr.GetInstance("http://10.10.10.1:8191", "", "socks5://10.10.10.1:7900") if err != nil { t.Error(err) return } - resp, err := f.GetV1("https://nopecha.com/demo/cloudflare", nil) + resp, err := f.GetV1("https://x1337x.cc/", nil) if err != nil { t.Error(err) return } if resp.Solution.Status != 200 { - t.Fail() - } else { - jsonBytes, _ := json.Marshal(resp) - t.Log(string(jsonBytes)) + t.Error("status code != 200", resp.Solution.Status) + return } + resp2, err := f.SimulateGet("https://x1337x.cc/user/FitGirl/", resp.Solution.UserAgent, resp.Solution.Cookies) + if err != nil { + t.Error(err) + return + } + if resp2.StatusCode != 200 { + t.Error("status code != 200", resp2.StatusCode) + return + } + body, err := io.ReadAll(resp2.Body) + if err != nil { + t.Error(err) + return + } + t.Log(string(body)) }