browserfetch 0.6.2__tar.gz → 0.8.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.
- {browserfetch-0.6.2 → browserfetch-0.8.0}/PKG-INFO +71 -71
- {browserfetch-0.6.2 → browserfetch-0.8.0}/README.rst +2 -1
- {browserfetch-0.6.2 → browserfetch-0.8.0}/browserfetch/__init__.py +48 -13
- {browserfetch-0.6.2 → browserfetch-0.8.0}/browserfetch/__main__.py +1 -1
- browserfetch-0.8.0/browserfetch/browserfetch.js +120 -0
- {browserfetch-0.6.2 → browserfetch-0.8.0}/pyproject.toml +17 -20
- browserfetch-0.6.2/browserfetch/browserfetch.js +0 -75
- browserfetch-0.6.2/browserfetch.egg-info/PKG-INFO +0 -71
- browserfetch-0.6.2/browserfetch.egg-info/SOURCES.txt +0 -12
- browserfetch-0.6.2/browserfetch.egg-info/dependency_links.txt +0 -1
- browserfetch-0.6.2/browserfetch.egg-info/not-zip-safe +0 -1
- browserfetch-0.6.2/browserfetch.egg-info/requires.txt +0 -2
- browserfetch-0.6.2/browserfetch.egg-info/top_level.txt +0 -1
- browserfetch-0.6.2/setup.cfg +0 -4
- {browserfetch-0.6.2 → browserfetch-0.8.0}/LICENSE +0 -0
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: browserfetch
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: fetch in Python using your browser!
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
9
|
-
Requires-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
from
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
loop
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
The
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
*
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
..
|
|
69
|
-
..
|
|
70
|
-
..
|
|
71
|
-
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: browserfetch
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: fetch in Python using your browser!
|
|
5
|
+
Keywords: browser,fetch,python,cookies
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/x-rst
|
|
8
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
9
|
+
Requires-Dist: aiohttp
|
|
10
|
+
Requires-Dist: pyperclip
|
|
11
|
+
Project-URL: Homepage, https://github.com/5j9/browserfetch
|
|
12
|
+
|
|
13
|
+
Fetch using your browser.
|
|
14
|
+
|
|
15
|
+
Let the browser manage cookies for you.
|
|
16
|
+
|
|
17
|
+
⚠️ Incomplete. Not tested thoroughly. Consider using `Playwright`_, especially for more complex scenarios.
|
|
18
|
+
|
|
19
|
+
Usage
|
|
20
|
+
-----
|
|
21
|
+
1. You'll run a Python script containing some code like this:
|
|
22
|
+
|
|
23
|
+
.. code-block:: python
|
|
24
|
+
|
|
25
|
+
from asyncio import gather, new_event_loop
|
|
26
|
+
|
|
27
|
+
from browserfetch import fetch, get, post, run_server
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def main():
|
|
31
|
+
response1, response2, reponse3 = await gather(
|
|
32
|
+
get('https://example.com/path1', params={'a': 1}),
|
|
33
|
+
fetch('https://example.com/image.png'),
|
|
34
|
+
post('https://example.com/path2', data={'a': 1}),
|
|
35
|
+
)
|
|
36
|
+
# do stuff with retrieved responses
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
loop = new_event_loop()
|
|
40
|
+
loop.create_task(start_server())
|
|
41
|
+
loop.run_until_complete(main())
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
2. Open your browser, goto http://example.com (perhaps solve a captcha and log in).
|
|
45
|
+
3. Copy the contents of `browserfetch.js`_ file and paste it in browser's console. (You can use a browser extensions like violentmonkey_/tampermonkey_ to do this step for you.)
|
|
46
|
+
|
|
47
|
+
That's it! Your Python script starts handling requests.
|
|
48
|
+
The browser tab should remain open of-coarse.
|
|
49
|
+
|
|
50
|
+
The server can handle multiple websocket connections from different websites simultaneously.
|
|
51
|
+
|
|
52
|
+
How it works
|
|
53
|
+
------------
|
|
54
|
+
``browserfetch`` communicates with your browser using a websocket. The ``fetch`` function just passes the request to browser and it is the browser that handles the actual request. Response data is sent back to Python using the same WebSocket connection.
|
|
55
|
+
|
|
56
|
+
Motivations
|
|
57
|
+
-----------
|
|
58
|
+
* `browser_cookie3 stopped working on Chrome-based browsers`_. There is a workaround: ShadowCopy, but it requires admin privilege.
|
|
59
|
+
* Another issue with browser_cookie's approach is that it retrieves cookies from cookie files, but these files are not updated instantly. Thus, you might have to wait or retry a few times before you can successfully access newly set cookies.
|
|
60
|
+
* ShadowCopying and File access are slow and inefficient operations.
|
|
61
|
+
|
|
62
|
+
Downsides
|
|
63
|
+
---------
|
|
64
|
+
* Setting up ``browserfetch`` is more cumbersome since it requires running a Python server and also injecting a small script into the webpage. Using ``browser_cookie3`` might be a better choice if there are many websites that you need to communicate with.
|
|
65
|
+
|
|
66
|
+
.. _playwright: https://playwright.dev/python/docs/intro
|
|
67
|
+
.. _`browser_cookie3 stopped working on Chrome-based browsers`: https://github.com/borisbabic/browser_cookie3/issues/180
|
|
68
|
+
.. _tampermonkey: https://github.com/Tampermonkey/tampermonkey
|
|
69
|
+
.. _violentmonkey: https://github.com/violentmonkey/violentmonkey
|
|
70
|
+
.. _browserfetch.js: https://github.com/5j9/browserfetch/blob/master/browserfetch/browserfetch.js
|
|
71
|
+
|
|
@@ -2,7 +2,7 @@ Fetch using your browser.
|
|
|
2
2
|
|
|
3
3
|
Let the browser manage cookies for you.
|
|
4
4
|
|
|
5
|
-
⚠️
|
|
5
|
+
⚠️ Incomplete. Not tested thoroughly. Consider using `Playwright`_, especially for more complex scenarios.
|
|
6
6
|
|
|
7
7
|
Usage
|
|
8
8
|
-----
|
|
@@ -51,6 +51,7 @@ Downsides
|
|
|
51
51
|
---------
|
|
52
52
|
* Setting up ``browserfetch`` is more cumbersome since it requires running a Python server and also injecting a small script into the webpage. Using ``browser_cookie3`` might be a better choice if there are many websites that you need to communicate with.
|
|
53
53
|
|
|
54
|
+
.. _playwright: https://playwright.dev/python/docs/intro
|
|
54
55
|
.. _`browser_cookie3 stopped working on Chrome-based browsers`: https://github.com/borisbabic/browser_cookie3/issues/180
|
|
55
56
|
.. _tampermonkey: https://github.com/Tampermonkey/tampermonkey
|
|
56
57
|
.. _violentmonkey: https://github.com/violentmonkey/violentmonkey
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
__version__ = '0.
|
|
1
|
+
__version__ = '0.8.0'
|
|
2
2
|
|
|
3
3
|
import atexit
|
|
4
|
-
from asyncio import
|
|
4
|
+
from asyncio import (
|
|
5
|
+
AbstractEventLoop,
|
|
6
|
+
CancelledError,
|
|
7
|
+
Event,
|
|
8
|
+
Task,
|
|
9
|
+
get_running_loop,
|
|
10
|
+
wait_for,
|
|
11
|
+
)
|
|
5
12
|
from collections import defaultdict
|
|
6
13
|
from dataclasses import dataclass
|
|
7
14
|
from json import dumps, loads
|
|
@@ -15,9 +22,9 @@ from aiohttp.web_runner import AppRunner, TCPSite
|
|
|
15
22
|
|
|
16
23
|
logger = getLogger(__name__)
|
|
17
24
|
# maps host to its host_ready event or its websocket
|
|
18
|
-
hosts: dict[
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
hosts: dict[str, Event | WebSocketResponse | ClientWebSocketResponse] = (
|
|
26
|
+
defaultdict(Event)
|
|
27
|
+
)
|
|
21
28
|
# maps response event id to its response event or response dict
|
|
22
29
|
responses: dict[int, Event | dict] = {}
|
|
23
30
|
|
|
@@ -106,6 +113,7 @@ async def receive_responses(ws: WebSocketResponse | ClientWebSocketResponse):
|
|
|
106
113
|
|
|
107
114
|
|
|
108
115
|
routes = RouteTableDef()
|
|
116
|
+
PROTOCOL = '2'
|
|
109
117
|
|
|
110
118
|
|
|
111
119
|
@routes.get('/ws')
|
|
@@ -113,7 +121,10 @@ async def _(request):
|
|
|
113
121
|
ws = WebSocketResponse()
|
|
114
122
|
await ws.prepare(request)
|
|
115
123
|
|
|
116
|
-
host = await ws.receive_str()
|
|
124
|
+
host, _, version = (await ws.receive_str()).partition(' ')
|
|
125
|
+
assert (
|
|
126
|
+
version == PROTOCOL
|
|
127
|
+
), f'JavaScript protocol version: {version}, expected: {PROTOCOL}'
|
|
117
128
|
logger.info('registering host %s', host)
|
|
118
129
|
|
|
119
130
|
ws_or_e = hosts[host]
|
|
@@ -165,10 +176,29 @@ async def relay_client(server_host, server_port):
|
|
|
165
176
|
try:
|
|
166
177
|
await receive_responses(ws)
|
|
167
178
|
except TypeError:
|
|
168
|
-
logger.info('relay
|
|
179
|
+
logger.info('relay WebSocket was closed')
|
|
180
|
+
hosts.default_factory = Event
|
|
181
|
+
for host, ws_or_e in hosts.items():
|
|
182
|
+
if isinstance(ws_or_e, Event):
|
|
183
|
+
ws_or_e.clear()
|
|
184
|
+
else:
|
|
185
|
+
hosts[host] = Event()
|
|
186
|
+
await start_server(host=_host, port=_port)
|
|
169
187
|
return
|
|
170
188
|
|
|
171
189
|
|
|
190
|
+
async def evaluate(
|
|
191
|
+
string: str,
|
|
192
|
+
host: str,
|
|
193
|
+
timeout: int | float = 95,
|
|
194
|
+
):
|
|
195
|
+
"""Evaluate string in browser context and return JSON.stringify(result)."""
|
|
196
|
+
d = await _request(
|
|
197
|
+
host, {'action': 'eval', 'string': string, 'timeout': timeout}, None
|
|
198
|
+
)
|
|
199
|
+
return d['result']
|
|
200
|
+
|
|
201
|
+
|
|
172
202
|
async def fetch(
|
|
173
203
|
url: str,
|
|
174
204
|
*,
|
|
@@ -195,6 +225,7 @@ async def fetch(
|
|
|
195
225
|
d = await _request(
|
|
196
226
|
host,
|
|
197
227
|
{
|
|
228
|
+
'action': 'fetch',
|
|
198
229
|
'url': url,
|
|
199
230
|
'options': options,
|
|
200
231
|
'timeout': timeout,
|
|
@@ -268,12 +299,13 @@ app.add_routes(routes)
|
|
|
268
299
|
app_runner = AppRunner(app)
|
|
269
300
|
|
|
270
301
|
|
|
271
|
-
def
|
|
302
|
+
def _shutdown_server(loop: AbstractEventLoop):
|
|
272
303
|
logger.info('waiting for app_runner.cleanup()')
|
|
273
304
|
loop.run_until_complete(app_runner.cleanup())
|
|
274
305
|
|
|
275
306
|
|
|
276
|
-
def
|
|
307
|
+
def _cancel_relay_task(loop: AbstractEventLoop, task: Task):
|
|
308
|
+
logger.info('cancelling relay task')
|
|
277
309
|
task.cancel()
|
|
278
310
|
try:
|
|
279
311
|
loop.run_until_complete(task)
|
|
@@ -282,10 +314,13 @@ def shutdown_relay_client(loop, task: Task):
|
|
|
282
314
|
|
|
283
315
|
|
|
284
316
|
_server = False
|
|
317
|
+
_host = '127.0.0.1'
|
|
318
|
+
_port = 9404
|
|
285
319
|
|
|
286
320
|
|
|
287
|
-
async def start_server(*, host=
|
|
288
|
-
global _server
|
|
321
|
+
async def start_server(*, host=_host, port=_port):
|
|
322
|
+
global _server, _host, _port
|
|
323
|
+
_host, _port = host, port
|
|
289
324
|
loop = get_running_loop()
|
|
290
325
|
await app_runner.setup()
|
|
291
326
|
site = TCPSite(app_runner, host, port)
|
|
@@ -298,8 +333,8 @@ async def start_server(*, host='127.0.0.1', port=9404):
|
|
|
298
333
|
e,
|
|
299
334
|
)
|
|
300
335
|
relay_task = loop.create_task(relay_client(host, port))
|
|
301
|
-
atexit.register(
|
|
336
|
+
atexit.register(_cancel_relay_task, loop, relay_task)
|
|
302
337
|
else:
|
|
303
338
|
_server = True
|
|
304
|
-
atexit.register(
|
|
339
|
+
atexit.register(_shutdown_server, loop)
|
|
305
340
|
logger.info('server started at http://%s:%s', host, port)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// ==UserScript==
|
|
2
|
+
// @name browserfetch
|
|
3
|
+
// @namespace https://github.com/5j9/browserfetch
|
|
4
|
+
// @match https://example.com/
|
|
5
|
+
// @grant GM_registerMenuCommand
|
|
6
|
+
// ==/UserScript==
|
|
7
|
+
(() => {
|
|
8
|
+
/**
|
|
9
|
+
* @param {Blob} body
|
|
10
|
+
* @param {j} Object
|
|
11
|
+
* @returns {Promise<Blob>}
|
|
12
|
+
*/
|
|
13
|
+
async function doFetch(req, body) {
|
|
14
|
+
var returnData, response;
|
|
15
|
+
var options = req['options'] || {};
|
|
16
|
+
|
|
17
|
+
if (req['timeout']) {
|
|
18
|
+
options.signal = AbortSignal.timeout(req['timeout'] * 1000);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (body !== null) {
|
|
22
|
+
options.body = body;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
var r = await fetch(req['url'], options);
|
|
27
|
+
returnData = {
|
|
28
|
+
'event_id': req['event_id'],
|
|
29
|
+
'headers': Object.fromEntries([...r.headers]),
|
|
30
|
+
'ok': r.ok,
|
|
31
|
+
'redirected': r.redirected,
|
|
32
|
+
'status': r.status,
|
|
33
|
+
'status_text': r.statusText,
|
|
34
|
+
'type': r.type,
|
|
35
|
+
'url': r.url
|
|
36
|
+
};
|
|
37
|
+
response = await r.blob();
|
|
38
|
+
} catch (err) {
|
|
39
|
+
returnData = {
|
|
40
|
+
'event_id': req['event_id'],
|
|
41
|
+
'error': err.toString()
|
|
42
|
+
};
|
|
43
|
+
response = "";
|
|
44
|
+
};
|
|
45
|
+
return new Blob([new TextEncoder().encode(JSON.stringify(returnData)), "\0", response]);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
*
|
|
50
|
+
* @param {String} s
|
|
51
|
+
* @returns {Promise<Uint8Array>}
|
|
52
|
+
*/
|
|
53
|
+
async function doEval(req) {
|
|
54
|
+
var evalled, resp;
|
|
55
|
+
try {
|
|
56
|
+
evalled = eval(req['string']);
|
|
57
|
+
resp = {'result': evalled, 'event_id': req['event_id']};
|
|
58
|
+
} catch (err) {
|
|
59
|
+
resp = {'result': err.toString(), 'event_id': req['event_id']};
|
|
60
|
+
}
|
|
61
|
+
return new TextEncoder().encode(JSON.stringify(resp));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
*
|
|
66
|
+
* @param {ArrayBuffer} d
|
|
67
|
+
* @returns {Array.<{binaryPart: Uint8Array, jsonPart: Object}>}
|
|
68
|
+
*/
|
|
69
|
+
function parseData(d) {
|
|
70
|
+
var blob, jArray;
|
|
71
|
+
var dArray = new Uint8Array(d);
|
|
72
|
+
var nullIndex = dArray.indexOf(0);
|
|
73
|
+
if (nullIndex === -1) {
|
|
74
|
+
blob = null;
|
|
75
|
+
jArray = dArray;
|
|
76
|
+
} else {
|
|
77
|
+
blob = dArray.slice(nullIndex + 1);
|
|
78
|
+
jArray = dArray.slice(0, nullIndex)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return [blob, JSON.parse(new TextDecoder().decode(jArray))]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function connect() {
|
|
85
|
+
var protocol = '2'
|
|
86
|
+
var ws = new WebSocket("ws://127.0.0.1:9404/ws");
|
|
87
|
+
ws.binaryType = "arraybuffer";
|
|
88
|
+
|
|
89
|
+
ws.onopen = () => {
|
|
90
|
+
ws.send(location.host + ' ' + protocol);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
ws.onclose = () => {
|
|
94
|
+
console.error('browserfetch: WebSocket was closed; will retry in 5 seconds');
|
|
95
|
+
setTimeout(connect, 5000);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
ws.onmessage = async (evt) => {
|
|
99
|
+
var result, j, b;
|
|
100
|
+
[b, j] = parseData(evt.data);
|
|
101
|
+
switch (j['action']) {
|
|
102
|
+
case 'fetch':
|
|
103
|
+
result = await doFetch(j, b);
|
|
104
|
+
break;
|
|
105
|
+
case 'eval':
|
|
106
|
+
result = await doEval(j);
|
|
107
|
+
}
|
|
108
|
+
ws.send(result);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
if (window.GM_registerMenuCommand) {
|
|
113
|
+
GM_registerMenuCommand(
|
|
114
|
+
'connect to browserfetch',
|
|
115
|
+
connect
|
|
116
|
+
);
|
|
117
|
+
} else {
|
|
118
|
+
connect();
|
|
119
|
+
}
|
|
120
|
+
})();
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = [
|
|
3
|
-
|
|
4
|
-
"wheel",
|
|
5
|
-
]
|
|
6
|
-
build-backend = "setuptools.build_meta"
|
|
7
|
-
|
|
8
|
-
[tool.isort]
|
|
9
|
-
profile = "black"
|
|
10
|
-
line_length = 79
|
|
11
|
-
combine_as_imports = true
|
|
2
|
+
requires = ['flit_core >=3.2,<4']
|
|
3
|
+
build-backend = 'flit_core.buildapi'
|
|
12
4
|
|
|
13
5
|
[project]
|
|
14
6
|
name = "browserfetch"
|
|
@@ -38,13 +30,18 @@ content-type = "text/x-rst"
|
|
|
38
30
|
[project.urls]
|
|
39
31
|
Homepage = "https://github.com/5j9/browserfetch"
|
|
40
32
|
|
|
41
|
-
[tool.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
[
|
|
50
|
-
|
|
33
|
+
[tool.ruff]
|
|
34
|
+
line-length = 79
|
|
35
|
+
format.quote-style = 'single'
|
|
36
|
+
lint.isort.combine-as-imports = true
|
|
37
|
+
lint.extend-select = [
|
|
38
|
+
'I', # isort
|
|
39
|
+
'UP', # pyupgrade
|
|
40
|
+
]
|
|
41
|
+
lint.ignore = [
|
|
42
|
+
'UP027', # list comprehensions are faster than generator expressions
|
|
43
|
+
'E721', # Do not compare types, use `isinstance()`
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[tool.pytest.ini_options]
|
|
47
|
+
addopts = '--quiet --tb=short'
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
// ==UserScript==
|
|
2
|
-
// @name browserfetch
|
|
3
|
-
// @namespace https://github.com/5j9/browserfetch
|
|
4
|
-
// @match https://example.com/
|
|
5
|
-
// @grant GM_registerMenuCommand
|
|
6
|
-
// ==/UserScript==
|
|
7
|
-
(() => {
|
|
8
|
-
function connect() {
|
|
9
|
-
var ws = new WebSocket("ws://127.0.0.1:9404/ws");
|
|
10
|
-
ws.binaryType = "arraybuffer";
|
|
11
|
-
|
|
12
|
-
ws.onopen = () => {
|
|
13
|
-
ws.send(location.host);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
ws.onclose = function () {
|
|
17
|
-
console.error('browserfetch: WebSocket was closed; will retry in 5 seconds');
|
|
18
|
-
setTimeout(connect, 5000);
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
ws.onmessage = async (evt) => {
|
|
22
|
-
var returnData, responseBlob, body, jArray;
|
|
23
|
-
var requestArray = new Uint8Array(evt.data);
|
|
24
|
-
var nullIndex = requestArray.indexOf(0);
|
|
25
|
-
if (nullIndex === -1) {
|
|
26
|
-
body = null;
|
|
27
|
-
jArray = requestArray;
|
|
28
|
-
} else {
|
|
29
|
-
body = requestArray.slice(nullIndex + 1);
|
|
30
|
-
jArray = requestArray.slice(0, nullIndex)
|
|
31
|
-
}
|
|
32
|
-
var j = JSON.parse(new TextDecoder().decode(jArray));
|
|
33
|
-
var options = j['options'] || {};
|
|
34
|
-
|
|
35
|
-
if (j['timeout']) {
|
|
36
|
-
options.signal = AbortSignal.timeout(j['timeout'] * 1000);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (body !== null) {
|
|
40
|
-
options.body = body;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
var r = await fetch(j['url'], options);
|
|
45
|
-
returnData = {
|
|
46
|
-
'event_id': j['event_id'],
|
|
47
|
-
'headers': Object.fromEntries([...r.headers]),
|
|
48
|
-
'ok': r.ok,
|
|
49
|
-
'redirected': r.redirected,
|
|
50
|
-
'status': r.status,
|
|
51
|
-
'status_text': r.statusText,
|
|
52
|
-
'type': r.type,
|
|
53
|
-
'url': r.url
|
|
54
|
-
};
|
|
55
|
-
responseBlob = await r.blob();
|
|
56
|
-
} catch (err) {
|
|
57
|
-
returnData = {
|
|
58
|
-
'event_id': j['event_id'],
|
|
59
|
-
'error': err.toString()
|
|
60
|
-
};
|
|
61
|
-
responseBlob = "";
|
|
62
|
-
};
|
|
63
|
-
ws.send(new Blob([new TextEncoder().encode(JSON.stringify(returnData)), "\0", responseBlob]));
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
if (window.GM_registerMenuCommand) {
|
|
68
|
-
GM_registerMenuCommand(
|
|
69
|
-
'connect to browserfetch',
|
|
70
|
-
connect
|
|
71
|
-
);
|
|
72
|
-
} else {
|
|
73
|
-
connect();
|
|
74
|
-
}
|
|
75
|
-
})();
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: browserfetch
|
|
3
|
-
Version: 0.6.2
|
|
4
|
-
Summary: fetch in Python using your browser!
|
|
5
|
-
License: GNU General Public License v3 (GPLv3)
|
|
6
|
-
Project-URL: Homepage, https://github.com/5j9/browserfetch
|
|
7
|
-
Keywords: browser,fetch,python,cookies
|
|
8
|
-
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
9
|
-
Requires-Python: >=3.11
|
|
10
|
-
Description-Content-Type: text/x-rst
|
|
11
|
-
License-File: LICENSE
|
|
12
|
-
Requires-Dist: aiohttp
|
|
13
|
-
Requires-Dist: pyperclip
|
|
14
|
-
|
|
15
|
-
Fetch using your browser.
|
|
16
|
-
|
|
17
|
-
Let the browser manage cookies for you.
|
|
18
|
-
|
|
19
|
-
⚠️ This project is a very simple implementation. Not tested thoroughly. Consider it a proof of concept.
|
|
20
|
-
|
|
21
|
-
Usage
|
|
22
|
-
-----
|
|
23
|
-
1. You'll run a Python script containing some code like this:
|
|
24
|
-
|
|
25
|
-
.. code-block:: python
|
|
26
|
-
|
|
27
|
-
from asyncio import gather, new_event_loop
|
|
28
|
-
|
|
29
|
-
from browserfetch import fetch, get, post, run_server
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
async def main():
|
|
33
|
-
response1, response2, reponse3 = await gather(
|
|
34
|
-
get('https://example.com/path1', params={'a': 1}),
|
|
35
|
-
fetch('https://example.com/image.png'),
|
|
36
|
-
post('https://example.com/path2', data={'a': 1}),
|
|
37
|
-
)
|
|
38
|
-
# do stuff with retrieved responses
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
loop = new_event_loop()
|
|
42
|
-
loop.create_task(start_server())
|
|
43
|
-
loop.run_until_complete(main())
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
2. Open your browser, goto http://example.com (perhaps solve a captcha and log in).
|
|
47
|
-
3. Copy the contents of `browserfetch.js`_ file and paste it in browser's console. (You can use a browser extensions like violentmonkey_/tampermonkey_ to do this step for you.)
|
|
48
|
-
|
|
49
|
-
That's it! Your Python script starts handling requests.
|
|
50
|
-
The browser tab should remain open of-coarse.
|
|
51
|
-
|
|
52
|
-
The server can handle multiple websocket connections from different websites simultaneously.
|
|
53
|
-
|
|
54
|
-
How it works
|
|
55
|
-
------------
|
|
56
|
-
``browserfetch`` communicates with your browser using a websocket. The ``fetch`` function just passes the request to browser and it is the browser that handles the actual request. Response data is sent back to Python using the same WebSocket connection.
|
|
57
|
-
|
|
58
|
-
Motivations
|
|
59
|
-
-----------
|
|
60
|
-
* `browser_cookie3 stopped working on Chrome-based browsers`_. There is a workaround: ShadowCopy, but it requires admin privilege.
|
|
61
|
-
* Another issue with browser_cookie's approach is that it retrieves cookies from cookie files, but these files are not updated instantly. Thus, you might have to wait or retry a few times before you can successfully access newly set cookies.
|
|
62
|
-
* ShadowCopying and File access are slow and inefficient operations.
|
|
63
|
-
|
|
64
|
-
Downsides
|
|
65
|
-
---------
|
|
66
|
-
* Setting up ``browserfetch`` is more cumbersome since it requires running a Python server and also injecting a small script into the webpage. Using ``browser_cookie3`` might be a better choice if there are many websites that you need to communicate with.
|
|
67
|
-
|
|
68
|
-
.. _`browser_cookie3 stopped working on Chrome-based browsers`: https://github.com/borisbabic/browser_cookie3/issues/180
|
|
69
|
-
.. _tampermonkey: https://github.com/Tampermonkey/tampermonkey
|
|
70
|
-
.. _violentmonkey: https://github.com/violentmonkey/violentmonkey
|
|
71
|
-
.. _browserfetch.js: https://github.com/5j9/browserfetch/blob/master/browserfetch/browserfetch.js
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
LICENSE
|
|
2
|
-
README.rst
|
|
3
|
-
pyproject.toml
|
|
4
|
-
browserfetch/__init__.py
|
|
5
|
-
browserfetch/__main__.py
|
|
6
|
-
browserfetch/browserfetch.js
|
|
7
|
-
browserfetch.egg-info/PKG-INFO
|
|
8
|
-
browserfetch.egg-info/SOURCES.txt
|
|
9
|
-
browserfetch.egg-info/dependency_links.txt
|
|
10
|
-
browserfetch.egg-info/not-zip-safe
|
|
11
|
-
browserfetch.egg-info/requires.txt
|
|
12
|
-
browserfetch.egg-info/top_level.txt
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
browserfetch
|
browserfetch-0.6.2/setup.cfg
DELETED
|
File without changes
|