browserfetch 0.7.0__tar.gz → 0.8.1__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.7.0 → browserfetch-0.8.1}/PKG-INFO +71 -71
- {browserfetch-0.7.0 → browserfetch-0.8.1}/README.rst +2 -1
- {browserfetch-0.7.0 → browserfetch-0.8.1}/browserfetch/__init__.py +24 -7
- browserfetch-0.8.1/browserfetch/browserfetch.js +130 -0
- {browserfetch-0.7.0 → browserfetch-0.8.1}/pyproject.toml +16 -19
- browserfetch-0.7.0/browserfetch/browserfetch.js +0 -75
- browserfetch-0.7.0/browserfetch.egg-info/PKG-INFO +0 -71
- browserfetch-0.7.0/browserfetch.egg-info/SOURCES.txt +0 -12
- browserfetch-0.7.0/browserfetch.egg-info/dependency_links.txt +0 -1
- browserfetch-0.7.0/browserfetch.egg-info/not-zip-safe +0 -1
- browserfetch-0.7.0/browserfetch.egg-info/requires.txt +0 -2
- browserfetch-0.7.0/browserfetch.egg-info/top_level.txt +0 -1
- browserfetch-0.7.0/setup.cfg +0 -4
- {browserfetch-0.7.0 → browserfetch-0.8.1}/LICENSE +0 -0
- {browserfetch-0.7.0 → browserfetch-0.8.1}/browserfetch/__main__.py +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.1
|
|
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,13 +1,13 @@
|
|
|
1
|
-
__version__ = '0.
|
|
1
|
+
__version__ = '0.8.1'
|
|
2
2
|
|
|
3
3
|
import atexit
|
|
4
4
|
from asyncio import (
|
|
5
5
|
AbstractEventLoop,
|
|
6
6
|
CancelledError,
|
|
7
7
|
Event,
|
|
8
|
+
Task,
|
|
8
9
|
get_running_loop,
|
|
9
10
|
wait_for,
|
|
10
|
-
Task,
|
|
11
11
|
)
|
|
12
12
|
from collections import defaultdict
|
|
13
13
|
from dataclasses import dataclass
|
|
@@ -22,9 +22,9 @@ from aiohttp.web_runner import AppRunner, TCPSite
|
|
|
22
22
|
|
|
23
23
|
logger = getLogger(__name__)
|
|
24
24
|
# maps host to its host_ready event or its websocket
|
|
25
|
-
hosts: dict[
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
hosts: dict[str, Event | WebSocketResponse | ClientWebSocketResponse] = (
|
|
26
|
+
defaultdict(Event)
|
|
27
|
+
)
|
|
28
28
|
# maps response event id to its response event or response dict
|
|
29
29
|
responses: dict[int, Event | dict] = {}
|
|
30
30
|
|
|
@@ -113,6 +113,7 @@ async def receive_responses(ws: WebSocketResponse | ClientWebSocketResponse):
|
|
|
113
113
|
|
|
114
114
|
|
|
115
115
|
routes = RouteTableDef()
|
|
116
|
+
PROTOCOL = '2'
|
|
116
117
|
|
|
117
118
|
|
|
118
119
|
@routes.get('/ws')
|
|
@@ -120,7 +121,10 @@ async def _(request):
|
|
|
120
121
|
ws = WebSocketResponse()
|
|
121
122
|
await ws.prepare(request)
|
|
122
123
|
|
|
123
|
-
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}'
|
|
124
128
|
logger.info('registering host %s', host)
|
|
125
129
|
|
|
126
130
|
ws_or_e = hosts[host]
|
|
@@ -183,6 +187,18 @@ async def relay_client(server_host, server_port):
|
|
|
183
187
|
return
|
|
184
188
|
|
|
185
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
|
+
|
|
186
202
|
async def fetch(
|
|
187
203
|
url: str,
|
|
188
204
|
*,
|
|
@@ -209,6 +225,7 @@ async def fetch(
|
|
|
209
225
|
d = await _request(
|
|
210
226
|
host,
|
|
211
227
|
{
|
|
228
|
+
'action': 'fetch',
|
|
212
229
|
'url': url,
|
|
213
230
|
'options': options,
|
|
214
231
|
'timeout': timeout,
|
|
@@ -288,7 +305,7 @@ def _shutdown_server(loop: AbstractEventLoop):
|
|
|
288
305
|
|
|
289
306
|
|
|
290
307
|
def _cancel_relay_task(loop: AbstractEventLoop, task: Task):
|
|
291
|
-
logger.info(
|
|
308
|
+
logger.info('cancelling relay task')
|
|
292
309
|
task.cancel()
|
|
293
310
|
try:
|
|
294
311
|
loop.run_until_complete(task)
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// ==UserScript==
|
|
2
|
+
// @name browserfetch
|
|
3
|
+
// @namespace https://github.com/5j9/browserfetch
|
|
4
|
+
// @match https://example.com/
|
|
5
|
+
// @grant GM_registerMenuCommand
|
|
6
|
+
// ==/UserScript==
|
|
7
|
+
// @ts-check
|
|
8
|
+
(() => {
|
|
9
|
+
/**
|
|
10
|
+
* @param {Uint8Array | null} body
|
|
11
|
+
* @param {Object} req
|
|
12
|
+
* @returns {Promise<Blob>}
|
|
13
|
+
*/
|
|
14
|
+
async function doFetch(req, body) {
|
|
15
|
+
var returnData, response;
|
|
16
|
+
var options = req['options'] || {};
|
|
17
|
+
|
|
18
|
+
if (req['timeout']) {
|
|
19
|
+
options.signal = AbortSignal.timeout(req['timeout'] * 1000);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (body !== null) {
|
|
23
|
+
options.body = body;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
var r = await fetch(req['url'], options);
|
|
28
|
+
returnData = {
|
|
29
|
+
'event_id': req['event_id'],
|
|
30
|
+
'headers': Object.fromEntries([...r.headers]),
|
|
31
|
+
'ok': r.ok,
|
|
32
|
+
'redirected': r.redirected,
|
|
33
|
+
'status': r.status,
|
|
34
|
+
'status_text': r.statusText,
|
|
35
|
+
'type': r.type,
|
|
36
|
+
'url': r.url
|
|
37
|
+
};
|
|
38
|
+
response = await r.blob();
|
|
39
|
+
} catch (err) {
|
|
40
|
+
returnData = {
|
|
41
|
+
'event_id': req['event_id'],
|
|
42
|
+
'error': err.toString()
|
|
43
|
+
};
|
|
44
|
+
response = "";
|
|
45
|
+
};
|
|
46
|
+
return new Blob([new TextEncoder().encode(JSON.stringify(returnData)), "\0", response]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
*
|
|
51
|
+
* @param {Object} req
|
|
52
|
+
* @returns {Promise<Uint8Array>}
|
|
53
|
+
*/
|
|
54
|
+
async function doEval(req) {
|
|
55
|
+
var evalled, resp;
|
|
56
|
+
try {
|
|
57
|
+
evalled = eval(req['string']);
|
|
58
|
+
resp = { 'result': evalled, 'event_id': req['event_id'] };
|
|
59
|
+
} catch (err) {
|
|
60
|
+
resp = { 'result': err.toString(), 'event_id': req['event_id'] };
|
|
61
|
+
}
|
|
62
|
+
return new TextEncoder().encode(JSON.stringify(resp));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
*
|
|
67
|
+
* @param {ArrayBuffer} d
|
|
68
|
+
* @returns {[Uint8Array | null, Object]}
|
|
69
|
+
*/
|
|
70
|
+
function parseData(d) {
|
|
71
|
+
var blob, jArray;
|
|
72
|
+
var dArray = new Uint8Array(d);
|
|
73
|
+
var nullIndex = dArray.indexOf(0);
|
|
74
|
+
if (nullIndex === -1) {
|
|
75
|
+
blob = null;
|
|
76
|
+
jArray = dArray;
|
|
77
|
+
} else {
|
|
78
|
+
blob = dArray.slice(nullIndex + 1);
|
|
79
|
+
jArray = dArray.slice(0, nullIndex)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return [blob, JSON.parse(new TextDecoder().decode(jArray))]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function connect() {
|
|
86
|
+
var protocol = '2'
|
|
87
|
+
var ws = new WebSocket("ws://127.0.0.1:9404/ws");
|
|
88
|
+
ws.binaryType = "arraybuffer";
|
|
89
|
+
|
|
90
|
+
ws.onopen = () => {
|
|
91
|
+
ws.send(location.host + ' ' + protocol);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
ws.onclose = () => {
|
|
95
|
+
console.error('browserfetch: WebSocket was closed; will retry in 5 seconds');
|
|
96
|
+
setTimeout(connect, 5000);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
ws.onmessage = async (evt) => {
|
|
100
|
+
var /**@type {Uint8Array | Blob} */ result, j, b;
|
|
101
|
+
[b, j] = parseData(evt.data);
|
|
102
|
+
switch (j['action']) {
|
|
103
|
+
case 'fetch':
|
|
104
|
+
result = await doFetch(j, b);
|
|
105
|
+
break;
|
|
106
|
+
case 'eval':
|
|
107
|
+
result = await doEval(j);
|
|
108
|
+
break;
|
|
109
|
+
default:
|
|
110
|
+
result = new TextEncoder().encode(JSON.stringify({
|
|
111
|
+
'event_id': j['event_id'],
|
|
112
|
+
'error': `Action ${j['action']} is not defined.`
|
|
113
|
+
}));
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
ws.send(result);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// @ts-ignore
|
|
121
|
+
if (window.GM_registerMenuCommand) {
|
|
122
|
+
// @ts-ignore
|
|
123
|
+
GM_registerMenuCommand(
|
|
124
|
+
'connect to browserfetch',
|
|
125
|
+
connect
|
|
126
|
+
);
|
|
127
|
+
} else {
|
|
128
|
+
connect();
|
|
129
|
+
}
|
|
130
|
+
})();
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = [
|
|
3
|
-
|
|
4
|
-
"wheel",
|
|
5
|
-
]
|
|
6
|
-
build-backend = "setuptools.build_meta"
|
|
2
|
+
requires = ['flit_core >=3.2,<4']
|
|
3
|
+
build-backend = 'flit_core.buildapi'
|
|
7
4
|
|
|
8
5
|
[project]
|
|
9
6
|
name = "browserfetch"
|
|
@@ -33,18 +30,18 @@ content-type = "text/x-rst"
|
|
|
33
30
|
[project.urls]
|
|
34
31
|
Homepage = "https://github.com/5j9/browserfetch"
|
|
35
32
|
|
|
36
|
-
[tool.setuptools]
|
|
37
|
-
packages = ["browserfetch"]
|
|
38
|
-
zip-safe = false
|
|
39
|
-
include-package-data = false
|
|
40
|
-
|
|
41
|
-
[tool.setuptools.dynamic.version]
|
|
42
|
-
attr = "browserfetch.__version__"
|
|
43
|
-
|
|
44
|
-
[tool.setuptools.package-data]
|
|
45
|
-
browserfetch = ["*.js"]
|
|
46
|
-
|
|
47
33
|
[tool.ruff]
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
isort.combine-as-imports = true
|
|
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.7.0
|
|
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.7.0/setup.cfg
DELETED
|
File without changes
|
|
File without changes
|