flaresolverr-cli 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flaresolverr_cli-0.2.0/LICENSE +21 -0
- flaresolverr_cli-0.2.0/PKG-INFO +189 -0
- flaresolverr_cli-0.2.0/README.md +145 -0
- flaresolverr_cli-0.2.0/cli.py +292 -0
- flaresolverr_cli-0.2.0/flaresolverr_cli.egg-info/PKG-INFO +189 -0
- flaresolverr_cli-0.2.0/flaresolverr_cli.egg-info/SOURCES.txt +16 -0
- flaresolverr_cli-0.2.0/flaresolverr_cli.egg-info/dependency_links.txt +1 -0
- flaresolverr_cli-0.2.0/flaresolverr_cli.egg-info/entry_points.txt +2 -0
- flaresolverr_cli-0.2.0/flaresolverr_cli.egg-info/requires.txt +7 -0
- flaresolverr_cli-0.2.0/flaresolverr_cli.egg-info/top_level.txt +3 -0
- flaresolverr_cli-0.2.0/flaresolverr_rpc.py +386 -0
- flaresolverr_cli-0.2.0/flaresolverr_session.py +341 -0
- flaresolverr_cli-0.2.0/setup.cfg +4 -0
- flaresolverr_cli-0.2.0/setup.py +68 -0
- flaresolverr_cli-0.2.0/tests/test_cli.py +553 -0
- flaresolverr_cli-0.2.0/tests/test_rpc.py +271 -0
- flaresolverr_cli-0.2.0/tests/test_session.py +542 -0
- flaresolverr_cli-0.2.0/tests/testconf.py +47 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Xavier-Lam
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flaresolverr-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A requests.Session that proxies through a FlareSolverr instance.
|
|
5
|
+
Home-page: https://github.com/Xavier-Lam/FlareSolverrSession
|
|
6
|
+
Author: Xavier-Lam
|
|
7
|
+
Author-email: xavierlam7@hotmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python
|
|
11
|
+
Classifier: Programming Language :: Python :: 2
|
|
12
|
+
Classifier: Programming Language :: Python :: 2.7
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
25
|
+
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: requests
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest; extra == "dev"
|
|
31
|
+
Requires-Dist: mock; python_version < "3" and extra == "dev"
|
|
32
|
+
Dynamic: author
|
|
33
|
+
Dynamic: author-email
|
|
34
|
+
Dynamic: classifier
|
|
35
|
+
Dynamic: description
|
|
36
|
+
Dynamic: description-content-type
|
|
37
|
+
Dynamic: home-page
|
|
38
|
+
Dynamic: license
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
Dynamic: provides-extra
|
|
41
|
+
Dynamic: requires-dist
|
|
42
|
+
Dynamic: requires-python
|
|
43
|
+
Dynamic: summary
|
|
44
|
+
|
|
45
|
+
# FlareSolverr Session
|
|
46
|
+
|
|
47
|
+
[](https://pypi.org/project/flaresolverr-session/)
|
|
48
|
+
[](https://github.com/Xavier-Lam/FlareSolverrSession/actions/workflows/ci.yml)
|
|
49
|
+
[](https://codecov.io/gh/Xavier-Lam/FlareSolverrSession)
|
|
50
|
+
|
|
51
|
+
A [`requests.Session`](https://docs.python-requests.org/) that transparently routes all HTTP requests through a [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr) instance, allowing you to bypass Cloudflare protection with a familiar Python API.
|
|
52
|
+
|
|
53
|
+
The project ships with a RPC client for direct access to the FlareSolverr JSON API, and a command-line interface (CLI) for quick requests and session management.
|
|
54
|
+
|
|
55
|
+
This project is not responsible for solving challenges itself, it only forwards requests to *FlareSolverr*. If *FlareSolverr* fails to solve a challenge, it will raise an exception. Any issues related to challenge solving should be reported to the *FlareSolverr* project.
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
## Installation
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install flaresolverr-session
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
or
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install flaresolverr-cli
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Prerequisites
|
|
71
|
+
|
|
72
|
+
You need a running [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr) instance. The quickest way is via Docker:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
docker run -d --name=flaresolverr -p 8191:8191 ghcr.io/flaresolverr/flaresolverr:latest
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Usage
|
|
79
|
+
|
|
80
|
+
### Basic Usage
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from flaresolverr_session import Session
|
|
84
|
+
|
|
85
|
+
with Session("http://localhost:8191/v1") as session:
|
|
86
|
+
response = session.get("https://example.com")
|
|
87
|
+
print(response.status_code)
|
|
88
|
+
print(response.text)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
It is recommended to set a persistent `session_id`.
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
session = Session(
|
|
95
|
+
"http://localhost:8191/v1",
|
|
96
|
+
session_id="my-persistent-session",
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
#### Response Object
|
|
101
|
+
A `FlareSolverr` object is attached to the `response` as `response.flaresolverr`. It contains metadata about the request and challenge solving process returned by *FlareSolverr*.
|
|
102
|
+
|
|
103
|
+
| Attribute | Description |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `flaresolverr.status` | `"ok"` on success |
|
|
106
|
+
| `flaresolverr.message` | Message from FlareSolverr (e.g. challenge status) |
|
|
107
|
+
| `flaresolverr.user_agent` | User-Agent used by FlareSolverr's browser |
|
|
108
|
+
| `flaresolverr.start` / `flaresolverr.end` | Request timestamps (ms) |
|
|
109
|
+
| `flaresolverr.version` | FlareSolverr server version |
|
|
110
|
+
|
|
111
|
+
#### Exception Handling
|
|
112
|
+
|
|
113
|
+
| Exception | Description |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `FlareSolverrError` | Base exception. Inherits from `requests.exceptions.RequestException`. |
|
|
116
|
+
| `FlareSolverrChallengeError` | Challenge could not be solved. |
|
|
117
|
+
| `FlareSolverrCaptchaError` | CAPTCHA detected. Inherits from `FlareSolverrChallengeError`. |
|
|
118
|
+
| `FlareSolverrTimeoutError` | Request timed out. |
|
|
119
|
+
| `FlareSolverrSessionError` | Session creation/destruction failed. |
|
|
120
|
+
| `FlareSolverrUnsupportedMethodError` | Unsupported HTTP method or content type. |
|
|
121
|
+
|
|
122
|
+
### Command-Line Interface
|
|
123
|
+
|
|
124
|
+
After installation, you can use the `flaresolverr-cli` command. It is a convenient CLI tool to send HTTP requests through FlareSolverr and manage sessions.
|
|
125
|
+
|
|
126
|
+
It will output json response from FlareSolverr. If the FlareSolverr URL is not provided via `-f`, it will use the `FLARESOLVERR_URL` environment variable (defaulting to `http://localhost:8191/v1`).
|
|
127
|
+
|
|
128
|
+
#### Sending requests
|
|
129
|
+
|
|
130
|
+
The `request` command is the default — you can omit the word `request`:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
flaresolverr-cli https://example.com -o output.html
|
|
134
|
+
|
|
135
|
+
# GET with a custom FlareSolverr URL
|
|
136
|
+
flaresolverr-cli -f http://localhost:8191/v1 https://example.com
|
|
137
|
+
|
|
138
|
+
# POST with form data (data implies POST)
|
|
139
|
+
flaresolverr-cli https://example.com -d "key=value&foo=bar"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### Managing sessions
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# Create a session (auto-generated name)
|
|
146
|
+
flaresolverr-cli -f http://localhost:8191/v1 session create my-session
|
|
147
|
+
|
|
148
|
+
# List all active sessions
|
|
149
|
+
flaresolverr-cli session list
|
|
150
|
+
|
|
151
|
+
# Destroy a session
|
|
152
|
+
flaresolverr-cli session destroy my-session
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### RPC Tool
|
|
156
|
+
|
|
157
|
+
The `flaresolverr_rpc` module provides a programmatic interface to the FlareSolverr JSON API, useful when you need low-level access to the raw API responses.
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from flaresolverr_rpc import RPC
|
|
161
|
+
|
|
162
|
+
with RPC("http://localhost:8191/v1") as rpc:
|
|
163
|
+
# Session management
|
|
164
|
+
rpc.session.create(session_id="my-session", proxy="http://proxy:8080")
|
|
165
|
+
sessions = rpc.session.list()
|
|
166
|
+
print(sessions["sessions"])
|
|
167
|
+
|
|
168
|
+
# HTTP requests
|
|
169
|
+
result = rpc.request.get("https://example.com", session_id="my-session")
|
|
170
|
+
print(result["solution"]["url"])
|
|
171
|
+
print(result["solution"]["response"]) # HTML body
|
|
172
|
+
|
|
173
|
+
result = rpc.request.post(
|
|
174
|
+
"https://example.com",
|
|
175
|
+
data="key=value",
|
|
176
|
+
session_id="my-session",
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Cleanup
|
|
180
|
+
rpc.session.destroy("my-session")
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
All methods return the raw JSON response dict from FlareSolverr.
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
## Limitations
|
|
187
|
+
|
|
188
|
+
- **Only GET and `application/x-www-form-urlencoded` POST** are supported. Otherwise, it will raise `FlareSolverrUnsupportedMethodError`.
|
|
189
|
+
- **Headers returned by FlareSolverr** may be empty for some sites, depending on the FlareSolverr version and configuration. Empty HTTP status will be regarded as `200`. See [FlareSolverr#1162](https://github.com/FlareSolverr/FlareSolverr/issues/1162).
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# FlareSolverr Session
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/flaresolverr-session/)
|
|
4
|
+
[](https://github.com/Xavier-Lam/FlareSolverrSession/actions/workflows/ci.yml)
|
|
5
|
+
[](https://codecov.io/gh/Xavier-Lam/FlareSolverrSession)
|
|
6
|
+
|
|
7
|
+
A [`requests.Session`](https://docs.python-requests.org/) that transparently routes all HTTP requests through a [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr) instance, allowing you to bypass Cloudflare protection with a familiar Python API.
|
|
8
|
+
|
|
9
|
+
The project ships with a RPC client for direct access to the FlareSolverr JSON API, and a command-line interface (CLI) for quick requests and session management.
|
|
10
|
+
|
|
11
|
+
This project is not responsible for solving challenges itself, it only forwards requests to *FlareSolverr*. If *FlareSolverr* fails to solve a challenge, it will raise an exception. Any issues related to challenge solving should be reported to the *FlareSolverr* project.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install flaresolverr-session
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
or
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install flaresolverr-cli
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Prerequisites
|
|
27
|
+
|
|
28
|
+
You need a running [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr) instance. The quickest way is via Docker:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
docker run -d --name=flaresolverr -p 8191:8191 ghcr.io/flaresolverr/flaresolverr:latest
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
### Basic Usage
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from flaresolverr_session import Session
|
|
40
|
+
|
|
41
|
+
with Session("http://localhost:8191/v1") as session:
|
|
42
|
+
response = session.get("https://example.com")
|
|
43
|
+
print(response.status_code)
|
|
44
|
+
print(response.text)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
It is recommended to set a persistent `session_id`.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
session = Session(
|
|
51
|
+
"http://localhost:8191/v1",
|
|
52
|
+
session_id="my-persistent-session",
|
|
53
|
+
)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
#### Response Object
|
|
57
|
+
A `FlareSolverr` object is attached to the `response` as `response.flaresolverr`. It contains metadata about the request and challenge solving process returned by *FlareSolverr*.
|
|
58
|
+
|
|
59
|
+
| Attribute | Description |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `flaresolverr.status` | `"ok"` on success |
|
|
62
|
+
| `flaresolverr.message` | Message from FlareSolverr (e.g. challenge status) |
|
|
63
|
+
| `flaresolverr.user_agent` | User-Agent used by FlareSolverr's browser |
|
|
64
|
+
| `flaresolverr.start` / `flaresolverr.end` | Request timestamps (ms) |
|
|
65
|
+
| `flaresolverr.version` | FlareSolverr server version |
|
|
66
|
+
|
|
67
|
+
#### Exception Handling
|
|
68
|
+
|
|
69
|
+
| Exception | Description |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `FlareSolverrError` | Base exception. Inherits from `requests.exceptions.RequestException`. |
|
|
72
|
+
| `FlareSolverrChallengeError` | Challenge could not be solved. |
|
|
73
|
+
| `FlareSolverrCaptchaError` | CAPTCHA detected. Inherits from `FlareSolverrChallengeError`. |
|
|
74
|
+
| `FlareSolverrTimeoutError` | Request timed out. |
|
|
75
|
+
| `FlareSolverrSessionError` | Session creation/destruction failed. |
|
|
76
|
+
| `FlareSolverrUnsupportedMethodError` | Unsupported HTTP method or content type. |
|
|
77
|
+
|
|
78
|
+
### Command-Line Interface
|
|
79
|
+
|
|
80
|
+
After installation, you can use the `flaresolverr-cli` command. It is a convenient CLI tool to send HTTP requests through FlareSolverr and manage sessions.
|
|
81
|
+
|
|
82
|
+
It will output json response from FlareSolverr. If the FlareSolverr URL is not provided via `-f`, it will use the `FLARESOLVERR_URL` environment variable (defaulting to `http://localhost:8191/v1`).
|
|
83
|
+
|
|
84
|
+
#### Sending requests
|
|
85
|
+
|
|
86
|
+
The `request` command is the default — you can omit the word `request`:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
flaresolverr-cli https://example.com -o output.html
|
|
90
|
+
|
|
91
|
+
# GET with a custom FlareSolverr URL
|
|
92
|
+
flaresolverr-cli -f http://localhost:8191/v1 https://example.com
|
|
93
|
+
|
|
94
|
+
# POST with form data (data implies POST)
|
|
95
|
+
flaresolverr-cli https://example.com -d "key=value&foo=bar"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### Managing sessions
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# Create a session (auto-generated name)
|
|
102
|
+
flaresolverr-cli -f http://localhost:8191/v1 session create my-session
|
|
103
|
+
|
|
104
|
+
# List all active sessions
|
|
105
|
+
flaresolverr-cli session list
|
|
106
|
+
|
|
107
|
+
# Destroy a session
|
|
108
|
+
flaresolverr-cli session destroy my-session
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### RPC Tool
|
|
112
|
+
|
|
113
|
+
The `flaresolverr_rpc` module provides a programmatic interface to the FlareSolverr JSON API, useful when you need low-level access to the raw API responses.
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from flaresolverr_rpc import RPC
|
|
117
|
+
|
|
118
|
+
with RPC("http://localhost:8191/v1") as rpc:
|
|
119
|
+
# Session management
|
|
120
|
+
rpc.session.create(session_id="my-session", proxy="http://proxy:8080")
|
|
121
|
+
sessions = rpc.session.list()
|
|
122
|
+
print(sessions["sessions"])
|
|
123
|
+
|
|
124
|
+
# HTTP requests
|
|
125
|
+
result = rpc.request.get("https://example.com", session_id="my-session")
|
|
126
|
+
print(result["solution"]["url"])
|
|
127
|
+
print(result["solution"]["response"]) # HTML body
|
|
128
|
+
|
|
129
|
+
result = rpc.request.post(
|
|
130
|
+
"https://example.com",
|
|
131
|
+
data="key=value",
|
|
132
|
+
session_id="my-session",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Cleanup
|
|
136
|
+
rpc.session.destroy("my-session")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
All methods return the raw JSON response dict from FlareSolverr.
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
## Limitations
|
|
143
|
+
|
|
144
|
+
- **Only GET and `application/x-www-form-urlencoded` POST** are supported. Otherwise, it will raise `FlareSolverrUnsupportedMethodError`.
|
|
145
|
+
- **Headers returned by FlareSolverr** may be empty for some sites, depending on the FlareSolverr version and configuration. Empty HTTP status will be regarded as `200`. See [FlareSolverr#1162](https://github.com/FlareSolverr/FlareSolverr/issues/1162).
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
from __future__ import print_function
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
from flaresolverr_rpc import RPC
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main(argv=None):
|
|
15
|
+
if argv is None:
|
|
16
|
+
argv = sys.argv[1:]
|
|
17
|
+
|
|
18
|
+
main_parser = _build_main_parser()
|
|
19
|
+
|
|
20
|
+
# Handle top-level -h/--help when no command specified
|
|
21
|
+
if not argv or argv == ["-h"] or argv == ["--help"]:
|
|
22
|
+
main_parser.print_help()
|
|
23
|
+
return 0
|
|
24
|
+
|
|
25
|
+
first_args, remaining = main_parser.parse_known_args(argv)
|
|
26
|
+
command = first_args.command
|
|
27
|
+
|
|
28
|
+
if command == "session":
|
|
29
|
+
parser = _build_session_parser()
|
|
30
|
+
args = parser.parse_args([command] + remaining)
|
|
31
|
+
rpc = RPC(first_args.flaresolverr_url)
|
|
32
|
+
res = _handle_session(rpc, args)
|
|
33
|
+
_format_output(res)
|
|
34
|
+
return 0
|
|
35
|
+
|
|
36
|
+
if command == "request":
|
|
37
|
+
request_argv = remaining
|
|
38
|
+
elif command is not None:
|
|
39
|
+
request_argv = [command] + remaining
|
|
40
|
+
else:
|
|
41
|
+
main_parser.print_help()
|
|
42
|
+
return 0
|
|
43
|
+
|
|
44
|
+
req_parser = _build_request_parser()
|
|
45
|
+
args = req_parser.parse_args(request_argv)
|
|
46
|
+
rpc = RPC(first_args.flaresolverr_url)
|
|
47
|
+
res = _handle_request(rpc, args)
|
|
48
|
+
_truncate_response_body(res)
|
|
49
|
+
_format_output(res)
|
|
50
|
+
return 0
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _build_main_parser():
|
|
54
|
+
parser = argparse.ArgumentParser(
|
|
55
|
+
prog="flaresolverr-cli",
|
|
56
|
+
description="Interact with a FlareSolverr instance",
|
|
57
|
+
add_help=False,
|
|
58
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
59
|
+
epilog=(
|
|
60
|
+
"commands:\n"
|
|
61
|
+
" session Manage FlareSolverr sessions\n"
|
|
62
|
+
" request (default) Send an HTTP request through FlareSolverr\n\n"
|
|
63
|
+
"Run 'flaresolverr-cli session --help' or "
|
|
64
|
+
"'flaresolverr-cli request --help' for more information."
|
|
65
|
+
),
|
|
66
|
+
)
|
|
67
|
+
parser.add_argument(
|
|
68
|
+
"-f",
|
|
69
|
+
"--flaresolverr",
|
|
70
|
+
dest="flaresolverr_url",
|
|
71
|
+
default=os.environ.get("FLARESOLVERR_URL", "http://localhost:8191/v1"),
|
|
72
|
+
help=(
|
|
73
|
+
"FlareSolverr API endpoint (default: FLARESOLVERR_URL "
|
|
74
|
+
"env var or http://localhost:8191/v1)"
|
|
75
|
+
),
|
|
76
|
+
)
|
|
77
|
+
parser.add_argument("command", nargs="?", default=None)
|
|
78
|
+
return parser
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _build_session_parser():
|
|
82
|
+
parser = argparse.ArgumentParser(
|
|
83
|
+
prog="flaresolverr-cli",
|
|
84
|
+
description="Interact with a FlareSolverr instance",
|
|
85
|
+
)
|
|
86
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
87
|
+
session_parser = subparsers.add_parser(
|
|
88
|
+
"session",
|
|
89
|
+
help="Manage FlareSolverr sessions",
|
|
90
|
+
)
|
|
91
|
+
session_sub = session_parser.add_subparsers(dest="session_action")
|
|
92
|
+
session_sub.required = True
|
|
93
|
+
|
|
94
|
+
# session create
|
|
95
|
+
create_parser = session_sub.add_parser("create", help="Create a session")
|
|
96
|
+
create_parser.add_argument(
|
|
97
|
+
"name",
|
|
98
|
+
nargs="?",
|
|
99
|
+
default=None,
|
|
100
|
+
help="Optional session name",
|
|
101
|
+
)
|
|
102
|
+
create_parser.add_argument(
|
|
103
|
+
"--proxy",
|
|
104
|
+
default=None,
|
|
105
|
+
help="Proxy URL (e.g. http://proxy:8080)",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# session list
|
|
109
|
+
session_sub.add_parser("list", help="List active sessions")
|
|
110
|
+
|
|
111
|
+
# session destroy
|
|
112
|
+
destroy_parser = session_sub.add_parser("destroy", help="Destroy a session")
|
|
113
|
+
destroy_parser.add_argument(
|
|
114
|
+
"session_id",
|
|
115
|
+
help="Session identifier to destroy",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return parser
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _build_request_parser():
|
|
122
|
+
parser = argparse.ArgumentParser(
|
|
123
|
+
prog="flaresolverr-cli request",
|
|
124
|
+
description="Send an HTTP request through FlareSolverr",
|
|
125
|
+
)
|
|
126
|
+
parser.add_argument(
|
|
127
|
+
"url",
|
|
128
|
+
help="URL to request",
|
|
129
|
+
)
|
|
130
|
+
parser.add_argument(
|
|
131
|
+
"-m",
|
|
132
|
+
"--method",
|
|
133
|
+
default=None,
|
|
134
|
+
choices=["GET", "POST"],
|
|
135
|
+
help="HTTP method (default: GET, or POST when -d is given)",
|
|
136
|
+
)
|
|
137
|
+
parser.add_argument(
|
|
138
|
+
"-s",
|
|
139
|
+
"--session-id",
|
|
140
|
+
dest="session_id",
|
|
141
|
+
default=None,
|
|
142
|
+
help="FlareSolverr session id to use",
|
|
143
|
+
)
|
|
144
|
+
parser.add_argument(
|
|
145
|
+
"-d",
|
|
146
|
+
"--data",
|
|
147
|
+
default=None,
|
|
148
|
+
help="POST data (x-www-form-urlencoded)",
|
|
149
|
+
)
|
|
150
|
+
parser.add_argument(
|
|
151
|
+
"-o",
|
|
152
|
+
"--output",
|
|
153
|
+
dest="output_file",
|
|
154
|
+
default=None,
|
|
155
|
+
help="Write response body to file",
|
|
156
|
+
)
|
|
157
|
+
parser.add_argument(
|
|
158
|
+
"-t",
|
|
159
|
+
"--timeout",
|
|
160
|
+
type=int,
|
|
161
|
+
default=None,
|
|
162
|
+
help="Request timeout in milliseconds (default: 60000)",
|
|
163
|
+
)
|
|
164
|
+
parser.add_argument(
|
|
165
|
+
"--proxy",
|
|
166
|
+
default=None,
|
|
167
|
+
help="Proxy URL (e.g. http://proxy:8080)",
|
|
168
|
+
)
|
|
169
|
+
parser.add_argument(
|
|
170
|
+
"--session-ttl-minutes",
|
|
171
|
+
dest="session_ttl_minutes",
|
|
172
|
+
type=int,
|
|
173
|
+
default=None,
|
|
174
|
+
help="Auto-rotate sessions older than this many minutes",
|
|
175
|
+
)
|
|
176
|
+
parser.add_argument(
|
|
177
|
+
"--return-only-cookies",
|
|
178
|
+
dest="return_only_cookies",
|
|
179
|
+
action="store_true",
|
|
180
|
+
default=False,
|
|
181
|
+
help="Return only cookies, omitting the response body",
|
|
182
|
+
)
|
|
183
|
+
parser.add_argument(
|
|
184
|
+
"--return-screenshot",
|
|
185
|
+
dest="return_screenshot",
|
|
186
|
+
action="store_true",
|
|
187
|
+
default=False,
|
|
188
|
+
help="Include a Base64 PNG screenshot in the response",
|
|
189
|
+
)
|
|
190
|
+
parser.add_argument(
|
|
191
|
+
"--wait",
|
|
192
|
+
dest="wait_in_seconds",
|
|
193
|
+
type=int,
|
|
194
|
+
default=None,
|
|
195
|
+
help="Extra seconds to wait after the challenge is solved",
|
|
196
|
+
)
|
|
197
|
+
parser.add_argument(
|
|
198
|
+
"--disable-media",
|
|
199
|
+
dest="disable_media",
|
|
200
|
+
action="store_true",
|
|
201
|
+
default=False,
|
|
202
|
+
help="Disable loading images, CSS and fonts",
|
|
203
|
+
)
|
|
204
|
+
return parser
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _handle_session(rpc, args):
|
|
208
|
+
action = args.session_action
|
|
209
|
+
|
|
210
|
+
if action == "create":
|
|
211
|
+
return rpc.session.create(
|
|
212
|
+
session_id=getattr(args, "name", None),
|
|
213
|
+
proxy=getattr(args, "proxy", None),
|
|
214
|
+
)
|
|
215
|
+
elif action == "list":
|
|
216
|
+
return rpc.session.list()
|
|
217
|
+
elif action == "destroy":
|
|
218
|
+
return rpc.session.destroy(args.session_id)
|
|
219
|
+
else:
|
|
220
|
+
raise ValueError("Unknown session action: %s" % action)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def _handle_request(rpc, args):
|
|
224
|
+
"""Send a request through FlareSolverr and display the result.
|
|
225
|
+
|
|
226
|
+
Parameters:
|
|
227
|
+
rpc (RPC): RPC client instance.
|
|
228
|
+
args (argparse.Namespace): Parsed CLI arguments.
|
|
229
|
+
"""
|
|
230
|
+
method = getattr(args, "method", None)
|
|
231
|
+
data = getattr(args, "data", None)
|
|
232
|
+
if method is None:
|
|
233
|
+
method = "POST" if data else "GET"
|
|
234
|
+
|
|
235
|
+
kwargs = {}
|
|
236
|
+
session_id = getattr(args, "session_id", None)
|
|
237
|
+
if session_id:
|
|
238
|
+
kwargs["session_id"] = session_id
|
|
239
|
+
timeout = getattr(args, "timeout", None)
|
|
240
|
+
if timeout:
|
|
241
|
+
kwargs["max_timeout"] = timeout
|
|
242
|
+
proxy = getattr(args, "proxy", None)
|
|
243
|
+
if proxy:
|
|
244
|
+
kwargs["proxy"] = proxy
|
|
245
|
+
session_ttl_minutes = getattr(args, "session_ttl_minutes", None)
|
|
246
|
+
if session_ttl_minutes is not None:
|
|
247
|
+
kwargs["session_ttl_minutes"] = session_ttl_minutes
|
|
248
|
+
if getattr(args, "return_only_cookies", False):
|
|
249
|
+
kwargs["return_only_cookies"] = True
|
|
250
|
+
if getattr(args, "return_screenshot", False):
|
|
251
|
+
kwargs["return_screenshot"] = True
|
|
252
|
+
wait_in_seconds = getattr(args, "wait_in_seconds", None)
|
|
253
|
+
if wait_in_seconds is not None:
|
|
254
|
+
kwargs["wait_in_seconds"] = wait_in_seconds
|
|
255
|
+
if getattr(args, "disable_media", False):
|
|
256
|
+
kwargs["disable_media"] = True
|
|
257
|
+
|
|
258
|
+
if method == "POST":
|
|
259
|
+
result = rpc.request.post(args.url, data=data, **kwargs)
|
|
260
|
+
else:
|
|
261
|
+
result = rpc.request.get(args.url, **kwargs)
|
|
262
|
+
|
|
263
|
+
# Write body to file if requested
|
|
264
|
+
output_file = getattr(args, "output_file", None)
|
|
265
|
+
if output_file:
|
|
266
|
+
body = result.get("solution", {}).get("response", "")
|
|
267
|
+
if isinstance(body, bytes):
|
|
268
|
+
content = body
|
|
269
|
+
else:
|
|
270
|
+
content = body.encode("utf-8")
|
|
271
|
+
with open(output_file, "wb") as f:
|
|
272
|
+
f.write(content)
|
|
273
|
+
|
|
274
|
+
return result
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _truncate_response_body(data, max_length=200):
|
|
278
|
+
solution = data.get("solution")
|
|
279
|
+
if not solution:
|
|
280
|
+
return data
|
|
281
|
+
body = solution.get("response", "")
|
|
282
|
+
if body and len(body) > max_length:
|
|
283
|
+
solution["response"] = body[:max_length] + "...[%d letters]" % len(body)
|
|
284
|
+
return data
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _format_output(data):
|
|
288
|
+
print(json.dumps(data, indent=2))
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
if __name__ == "__main__":
|
|
292
|
+
sys.exit(main())
|