1+ // Author: Deci | Project: SmartImage.Lib | Name: FlareSolverr.cs
2+ // Date: 2024/10/16 @ 15:10:40
3+
4+ using System . Net ;
5+ using System . Net . Http . Json ;
6+ using System . Text ;
7+ using System . Text . Json ;
8+ using System . Text . Json . Serialization ;
9+
10+ namespace SmartImage . Lib . Clients ;
11+
12+ public class FlareSolverr
13+ {
14+
15+ private static readonly SemaphoreLocker Locker = new SemaphoreLocker ( ) ;
16+ private HttpClient _httpClient ;
17+ private readonly Uri _flareSolverrUri ;
18+
19+ public int MaxTimeout = 60000 ;
20+ public string ProxyUrl = "" ;
21+ public string ProxyUsername = null ;
22+ public string ProxyPassword = null ;
23+
24+ public FlareSolverr ( string flareSolverrApiUrl )
25+ {
26+ var apiUrl = flareSolverrApiUrl ;
27+
28+ if ( ! apiUrl . EndsWith ( "/" ) )
29+ apiUrl += "/" ;
30+ _flareSolverrUri = new Uri ( apiUrl + "v1" ) ;
31+ }
32+
33+ public Task < FlareSolverrRoot > Solve ( HttpRequestMessage request , string sessionId = "" )
34+ {
35+ return SendFlareSolverrRequestAsync ( GenerateFlareSolverrRequest ( request , sessionId ) ) ;
36+ }
37+
38+ public Task < FlareSolverrRoot > CreateSession ( )
39+ {
40+ var req = new FlareSolverrRequest
41+ {
42+ Command = "sessions.create" ,
43+ MaxTimeout = MaxTimeout ,
44+ Proxy = GetProxy ( )
45+ } ;
46+ return SendFlareSolverrRequestAsync ( GetSolverRequestContent ( req ) ) ;
47+ }
48+
49+ public Task < FlareSolverrRoot > ListSessionsAsync ( )
50+ {
51+ var req = new FlareSolverrRequest
52+ {
53+ Command = "sessions.list" ,
54+ MaxTimeout = MaxTimeout ,
55+ Proxy = GetProxy ( )
56+ } ;
57+ return SendFlareSolverrRequestAsync ( GetSolverRequestContent ( req ) ) ;
58+ }
59+
60+ public async Task < FlareSolverrRoot > DestroySessionAsync ( string sessionId )
61+ {
62+ var req = new FlareSolverrRequest
63+ {
64+ Command = "sessions.destroy" ,
65+ MaxTimeout = MaxTimeout ,
66+ Proxy = GetProxy ( ) ,
67+ Session = sessionId
68+ } ;
69+ return await SendFlareSolverrRequestAsync ( GetSolverRequestContent ( req ) ) ;
70+ }
71+
72+ private async Task < FlareSolverrRoot > SendFlareSolverrRequestAsync ( HttpContent flareSolverrRequest )
73+ {
74+ FlareSolverrRoot result = null ;
75+
76+ await Locker . LockAsync ( async ( ) =>
77+ {
78+ HttpResponseMessage response ;
79+
80+ try {
81+ _httpClient = new HttpClient ( ) ;
82+
83+ // wait 5 more seconds to make sure we return the FlareSolverr timeout message
84+ _httpClient . Timeout = TimeSpan . FromMilliseconds ( MaxTimeout + 5000 ) ;
85+ response = await _httpClient . PostAsync ( _flareSolverrUri , flareSolverrRequest ) ;
86+ }
87+ catch ( HttpRequestException e ) {
88+ throw new FlareSolverrException ( "Error connecting to FlareSolverr server: " + e ) ;
89+ }
90+ catch ( Exception e ) {
91+ throw new FlareSolverrException ( "Exception: " + e ) ;
92+ }
93+ finally {
94+ _httpClient . Dispose ( ) ;
95+ }
96+
97+ // Don't try parsing if FlareSolverr hasn't returned 200 or 500
98+ if ( response . StatusCode != HttpStatusCode . OK
99+ && response . StatusCode != HttpStatusCode . InternalServerError ) {
100+ throw new FlareSolverrException ( "HTTP StatusCode not 200 or 500. Status is :"
101+ + response . StatusCode ) ;
102+ }
103+
104+ var resContent = await response . Content . ReadAsStringAsync ( ) ;
105+
106+ try {
107+ result = JsonSerializer . Deserialize < FlareSolverrRoot > ( resContent , FlareSolverrHandler . s_jsonSerializerOptions ) ;
108+ }
109+ catch ( Exception ) {
110+ throw new FlareSolverrException ( "Error parsing response, check FlareSolverr. Response: "
111+ + resContent ) ;
112+ }
113+
114+ try {
115+ Enum . TryParse ( result . Status , true , out FlareSolverrStatusCode returnStatusCode ) ;
116+
117+ if ( returnStatusCode . Equals ( FlareSolverrStatusCode . ok ) ) {
118+ return result ;
119+ }
120+
121+ if ( returnStatusCode . Equals ( FlareSolverrStatusCode . warning ) ) {
122+ throw new FlareSolverrException (
123+ "FlareSolverr was able to process the request, but a captcha was detected. Message: "
124+ + result . Message ) ;
125+ }
126+
127+ if ( returnStatusCode . Equals ( FlareSolverrStatusCode . error ) ) {
128+ throw new FlareSolverrException (
129+ "FlareSolverr was unable to process the request, please check FlareSolverr logs. Message: "
130+ + result . Message ) ;
131+ }
132+
133+ throw new FlareSolverrException ( "Unable to map FlareSolverr returned status code, received code: "
134+ + result . Status + ". Message: " + result . Message ) ;
135+ }
136+ catch ( ArgumentException ) {
137+ throw new FlareSolverrException ( "Error parsing status code, check FlareSolverr log. Status: "
138+ + result . Status + ". Message: " + result . Message ) ;
139+ }
140+ } ) ;
141+
142+ return result ;
143+ }
144+
145+ private FlareSolverrRequestProxy GetProxy ( )
146+ {
147+ FlareSolverrRequestProxy proxy = null ;
148+
149+ if ( ! string . IsNullOrWhiteSpace ( ProxyUrl ) ) {
150+ proxy = new FlareSolverrRequestProxy
151+ {
152+ Url = ProxyUrl ,
153+ } ;
154+
155+ if ( ! string . IsNullOrWhiteSpace ( ProxyUsername ) ) {
156+ proxy . Username = ProxyUsername ;
157+ }
158+
159+ ;
160+
161+ if ( ! string . IsNullOrWhiteSpace ( ProxyPassword ) ) {
162+ proxy . Password = ProxyPassword ;
163+ }
164+
165+ ;
166+ }
167+
168+ return proxy ;
169+ }
170+
171+ private static HttpContent GetSolverRequestContent ( FlareSolverrRequest request )
172+ {
173+ var content = JsonContent . Create ( request , options : FlareSolverrHandler . s_jsonSerializerOptions ) ;
174+ // HttpContent content = new StringContent(payload, Encoding.UTF8, "application/json");
175+ return content ;
176+ }
177+
178+ private HttpContent GenerateFlareSolverrRequest ( HttpRequestMessage request , string sessionId = "" )
179+ {
180+ FlareSolverrRequest req ;
181+
182+ if ( string . IsNullOrWhiteSpace ( sessionId ) )
183+ sessionId = null ;
184+
185+ var url = request . RequestUri . ToString ( ) ;
186+
187+ FlareSolverrRequestProxy proxy = GetProxy ( ) ;
188+
189+ if ( request . Method == HttpMethod . Get ) {
190+ req = new FlareSolverrRequest
191+ {
192+ Command = "request.get" ,
193+ Url = url ,
194+ MaxTimeout = MaxTimeout ,
195+ Proxy = proxy ,
196+ Session = sessionId
197+ } ;
198+ }
199+ else if ( request . Method == HttpMethod . Post ) {
200+ // request.Content.GetType() doesn't work well when encoding != utf-8
201+ var contentMediaType = request . Content . Headers . ContentType ? . MediaType . ToLower ( ) ?? "<null>" ;
202+
203+ if ( contentMediaType . Contains ( "application/x-www-form-urlencoded" ) ) {
204+ req = new FlareSolverrRequest
205+ {
206+ Command = "request.post" ,
207+ Url = url ,
208+ PostData = request . Content . ReadAsStringAsync ( ) . Result ,
209+ MaxTimeout = MaxTimeout ,
210+ Proxy = proxy ,
211+ Session = sessionId
212+ } ;
213+ }
214+ else if ( contentMediaType . Contains ( "multipart/form-data" )
215+ || contentMediaType . Contains ( "text/html" ) ) {
216+ //TODO Implement - check if we just need to pass the content-type with the relevant headers
217+ throw new FlareSolverrException ( "Unimplemented POST Content-Type: " + contentMediaType ) ;
218+ }
219+ else {
220+ throw new FlareSolverrException ( "Unsupported POST Content-Type: " + contentMediaType ) ;
221+ }
222+ }
223+ else {
224+ throw new FlareSolverrException ( "Unsupported HttpMethod: " + request . Method ) ;
225+ }
226+
227+ return GetSolverRequestContent ( req ) ;
228+ }
229+
230+ }
0 commit comments