feat!: migrate simulate requests to resty v3 and add instance options
This commit is contained in:
+73
-115
@@ -3,44 +3,76 @@ package flaresolverr
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
fhttp "github.com/bogdanfinn/fhttp"
|
||||
tls_client "github.com/bogdanfinn/tls-client"
|
||||
"github.com/bogdanfinn/tls-client/profiles"
|
||||
"resty.dev/v3"
|
||||
)
|
||||
|
||||
var instances map[string]*Flaresolverr = make(map[string]*Flaresolverr)
|
||||
type instanceKey struct {
|
||||
url string
|
||||
sessionID string
|
||||
proxy string
|
||||
}
|
||||
|
||||
type instanceOptions struct {
|
||||
sessionID string
|
||||
proxy string
|
||||
}
|
||||
|
||||
type InstanceOption func(*instanceOptions)
|
||||
|
||||
func WithSessionID(sessionID string) InstanceOption {
|
||||
return func(opts *instanceOptions) {
|
||||
opts.sessionID = sessionID
|
||||
}
|
||||
}
|
||||
|
||||
func WithProxy(proxy string) InstanceOption {
|
||||
return func(opts *instanceOptions) {
|
||||
opts.proxy = proxy
|
||||
}
|
||||
}
|
||||
|
||||
var instances map[instanceKey]*Flaresolverr = make(map[instanceKey]*Flaresolverr)
|
||||
|
||||
type Flaresolverr struct {
|
||||
url string
|
||||
v1Url string
|
||||
sessionID string
|
||||
proxy string
|
||||
userAgent string
|
||||
cookies []*http.Cookie
|
||||
}
|
||||
|
||||
func GetInstance(flaresolverrURL string, sessionID string, proxy string) (*Flaresolverr, error) {
|
||||
if instance, ok := instances[flaresolverrURL]; ok {
|
||||
func GetInstance(flaresolverrURL string, opts ...InstanceOption) (*Flaresolverr, error) {
|
||||
config := instanceOptions{}
|
||||
for _, opt := range opts {
|
||||
if opt != nil {
|
||||
opt(&config)
|
||||
}
|
||||
}
|
||||
|
||||
key := instanceKey{
|
||||
url: flaresolverrURL,
|
||||
sessionID: config.sessionID,
|
||||
proxy: config.proxy,
|
||||
}
|
||||
|
||||
if instance, ok := instances[key]; ok {
|
||||
return instance, nil
|
||||
}
|
||||
flareSolverr := &Flaresolverr{
|
||||
url: flaresolverrURL,
|
||||
v1Url: strings.TrimSuffix(flaresolverrURL, "/") + "/v1",
|
||||
sessionID: sessionID,
|
||||
proxy: proxy,
|
||||
sessionID: config.sessionID,
|
||||
proxy: config.proxy,
|
||||
}
|
||||
instances[flaresolverrURL] = flareSolverr
|
||||
instances[key] = flareSolverr
|
||||
return flareSolverr, nil
|
||||
}
|
||||
|
||||
func (f *Flaresolverr) requestV1(req *V1RequestBase) (*V1ResponseBase, error) {
|
||||
resp, err := restyClient.R().SetBody(req).Post(f.v1Url)
|
||||
resp, err := NewRestyClient().R().SetBody(req).Post(f.v1Url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -48,7 +80,7 @@ func (f *Flaresolverr) requestV1(req *V1RequestBase) (*V1ResponseBase, error) {
|
||||
return nil, fmt.Errorf("request failed: status code: %v", resp.StatusCode())
|
||||
}
|
||||
var res V1ResponseBase
|
||||
err = json.Unmarshal(resp.Body(), &res)
|
||||
err = json.Unmarshal(resp.Bytes(), &res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -145,106 +177,32 @@ 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 ConvertCookiesToStr(cookies []*http.Cookie) string {
|
||||
c := []string{}
|
||||
for _, cookie := range cookies {
|
||||
c = append(c, fmt.Sprintf("%s=%s", cookie.Name, cookie.Value))
|
||||
}
|
||||
return strings.Join(c, "; ")
|
||||
}
|
||||
|
||||
func (f *Flaresolverr) simulateRequest(method string, url string, ua string, cookies []*http.Cookie, body io.Reader, opts ...tls_client.HttpClientOption) (*fhttp.Response, error) {
|
||||
currentUA := ua
|
||||
currentCookies := cookies
|
||||
|
||||
// Use cached credentials if arguments are empty
|
||||
if currentUA == "" {
|
||||
currentUA = f.userAgent
|
||||
func (f *Flaresolverr) simulateRequest(method string, url string, ua string, cookies []*http.Cookie, body any) (*resty.Response, error) {
|
||||
var resp *resty.Response
|
||||
var err error
|
||||
c := NewRestyClient()
|
||||
if f.proxy != "" {
|
||||
c = c.SetProxy(f.proxy)
|
||||
} else {
|
||||
c.RemoveProxy()
|
||||
}
|
||||
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(method, url, body)
|
||||
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)
|
||||
}
|
||||
req := c.R().
|
||||
SetHeader("User-Agent", ua).
|
||||
SetHeader("Cookie", ConvertCookiesToStr(cookies)).
|
||||
SetHeader("Accept-Encoding", "gzip, deflate")
|
||||
switch strings.ToUpper(method) {
|
||||
case "GET":
|
||||
resp, err = req.Get(url)
|
||||
case "POST":
|
||||
resp, err = req.SetBody(body).Post(url)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -254,10 +212,10 @@ func (f *Flaresolverr) simulateRequest(method string, url string, ua string, coo
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (f *Flaresolverr) SimulateGet(url string, ua string, cookies []*http.Cookie, opts ...tls_client.HttpClientOption) (*fhttp.Response, error) {
|
||||
return f.simulateRequest("GET", url, ua, cookies, nil, opts...)
|
||||
func (f *Flaresolverr) SimulateGet(url string, ua string, cookies []*http.Cookie) (*resty.Response, error) {
|
||||
return f.simulateRequest("GET", url, ua, cookies, nil)
|
||||
}
|
||||
|
||||
func (f *Flaresolverr) SimulatePost(url string, ua string, cookies []*http.Cookie, body io.Reader, opts ...tls_client.HttpClientOption) (*fhttp.Response, error) {
|
||||
return f.simulateRequest("POST", url, ua, cookies, body, opts...)
|
||||
func (f *Flaresolverr) SimulatePost(url string, ua string, cookies []*http.Cookie, body any) (*resty.Response, error) {
|
||||
return f.simulateRequest("POST", url, ua, cookies, body)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user