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.
@@ -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
+ [![PyPI version](https://badge.fury.io/py/flaresolverr-session.svg)](https://pypi.org/project/flaresolverr-session/)
48
+ [![CI](https://github.com/Xavier-Lam/FlareSolverrSession/actions/workflows/ci.yml/badge.svg)](https://github.com/Xavier-Lam/FlareSolverrSession/actions/workflows/ci.yml)
49
+ [![codecov](https://codecov.io/gh/Xavier-Lam/FlareSolverrSession/branch/master/graph/badge.svg)](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
+ [![PyPI version](https://badge.fury.io/py/flaresolverr-session.svg)](https://pypi.org/project/flaresolverr-session/)
4
+ [![CI](https://github.com/Xavier-Lam/FlareSolverrSession/actions/workflows/ci.yml/badge.svg)](https://github.com/Xavier-Lam/FlareSolverrSession/actions/workflows/ci.yml)
5
+ [![codecov](https://codecov.io/gh/Xavier-Lam/FlareSolverrSession/branch/master/graph/badge.svg)](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())