Bulk file downloader — real-Chrome extraction for datanodes.to, pure-HTTP extraction for fuckingfast.co, aiohttp streaming, and a GUI that runs on Edge WebView2.
Supported providers: datanodes.to · fuckingfast.co
Built with Python · Playwright · aiohttp · curl_cffi
Best tested:
~250 MB/son a 2.5 Gbps fiber — 23.5 GB across 47 files in ~3 minutes
195.7 MB/s · 124 fuckingfast links · 8 DL streams · 12.63 GB in 1m23s · ETA 3m35s — and not one browser window, because no datanodes link was in the batch.
| Live transfers | Engine log |
![]() |
![]() |
One browser decision, made once. BrowserGate in moon_extract.py owns the question of whether
Chrome is needed at all. A batch of fuckingfast.co links never launches it — not even the Playwright
driver's node process. A batch containing one datanodes.to link opens exactly one shared Chrome, on
demand, no matter how many extractors are running. The GUI and the CLI import the same gate, so they
cannot diverge, and tests/test_no_chrome.py asserts it for both in CI.
The UI pulls, Python never pushes. The page requests snapshot(cursor) about twelve times a
second instead of the engine writing into it. Every DOM write stays on the page's own timeline, so a
late snapshot costs a dropped frame instead of a stalled interface.
The log ring has a cursor. A bounded 6000-line ring plus a monotonic counter. The page asks for "everything after N"; if it fell behind further than the ring holds, it receives the oldest line still present rather than a silent gap it has no way to detect.
Untrusted input is clamped at one boundary. Everything the page sends passes through
Engine.apply_cfg(), which coerces and clamps every number before it can reach a semaphore. There is
one place to audit, not one per setting.
Measured, not asserted. 195.7 MB/s across 124 links on 8 download streams — 12.63 GB in 1m23s, 29 files done, 0 failed, no browser window opened. Method and instrumentation in docs/ENGINEERING_NOTES.md; the full design writeup is in docs/ARCHITECTURE.md.
The two providers stopped having anything in common, so the app stopped pretending they did.
| datanodes.to | fuckingfast.co | |
|---|---|---|
| Extraction | real Chrome over CDP + persistent profile | plain HTTPS with a Chrome TLS fingerprint |
| Browser | yes — pages on one window, one identity | none |
| Captcha | Turnstile: auto-solve, manual fallback | none |
| Cost per link | seconds | ~0.25 s |
| Settings | Pages, Captcha wait, Chrome path, API key |
nothing to tune |
A batch of fuckingfast links never launches Chrome — not even the Playwright driver's node process. A batch that contains one datanodes link opens exactly one shared Chrome, on demand, no matter how many extractors are running. The screenshot above is that fix, visible: 124 fuckingfast links moving at 195.7 MB/s with no browser window anywhere.
One BrowserGate in moon_extract.py owns that decision, so the GUI and the CLI behave the same
way — tests/test_no_chrome.py asserts it for both.
Moon Downloader is a client for links you already have. It automates the retrieval step — it does not search, index, or discover content, and it has no catalogue of any kind.
- What you download is your responsibility. Copyright, licensing and the terms of the hosts you point it at are yours to respect. This tool does not check any of that for you.
- The
datanodes.topath automates a challenge the site presents to visitors. That challenge is an anti-bot control, and automating it may be contrary to that host's terms of service. Decide whether that is acceptable for your use before you run it. - The defaults are deliberately conservative — 8 download streams, one shared browser, one identity. They can be raised. Do not use this to hammer a host.
- No affiliation. This project is not affiliated with, endorsed by, or supported by datanodes.to, fuckingfast.co, Cloudflare, or Microsoft. All trademarks belong to their respective owners.
- Provided as-is under the MIT licence, without warranty of any kind. See LICENSE.
Rightsholders and providers: if you want a path changed or removed, open an issue or use the contact in SECURITY.md.
- Install Python 3.10+ — check ✅ "Add Python to PATH"
- Double-click
start.bat - Done. The first run installs the dependencies and Chromium.
start.bat starts a loopback HTTP server and opens the GUI in Edge (or Chrome) with --app:
a window with no tabs and no address bar. No native GUI dependency, nothing to guess.
pip install -r requirements.txt
playwright install chromium
python moon_bridge.py # GUI
python moon_bridge.py --serve # server only, prints the URL
python moon_cli.py <url> ... -o <folder> # headless CLIOpen web/index.html in Chrome or Edge. It boots in demo mode with a synthetic engine.
- Links — per-host colouring while you paste, live count,
datanodes / fuckingfast / otherssplit - Per-method panels — common knobs in one card, datanodes in its own, fuckingfast declaring it has nothing to tune
- Live transfers — one row per file: progress ring, state, percentage, instantaneous speed. Active transfers sort above the finished tail
- Stats — speed (3 s rolling window + sparkline), completed, downloaded, byte-based ETA
- Pipeline — extraction and download tracked separately, because they run at the same time
- Log — the engine's own lines, tagged and coloured, capped at 2000
- English / Italian — switchable at runtime, English by default
- Settings and pasted links persist in
settings.json(atomic write)
| Setting | Range | Default | Applies to | Description |
|---|---|---|---|---|
| Extractors | 2 – 32 | 16 | both | Parallel extraction workers |
| DL streams | 2 – 48 | 48 | both | Concurrent download connections |
| Retries | 0 – 5 | 3 | both | Extraction retries per URL (network retries are separate) |
| Pages | 1 – 8 | 8 | datanodes | Tabs on the shared Chrome window — not separate windows |
| Captcha | 30 – 600 s | 30 | datanodes | Manual Turnstile wait |
| Chrome | path | autodetect | datanodes | chrome.exe to drive over CDP |
| API key | string | — | datanodes | Premium key → direct JSON, no browser, no captcha |
Fewer DL streams means more bandwidth per file; the pipe is still the ceiling.
Environment variables, the dedicated Chrome profile and the API-key limits are in docs/CONFIGURATION.md.
start.bat
└── moon_bridge.py loopback HTTP + token, launches Edge --app, OS dialogs
├── web/ index.html · styles.css · app.js (the GUI)
└── moon_engine.py headless engine: start/stop/snapshot
└── moon_extract.py datanodes (Chrome+CDP) · fuckingfast (curl_cffi)
BrowserGate: the launch, deferred
moon_cli.py headless CLI, same engine, same extraction layer
Both front-ends import the same moon_extract, so extraction, the Chrome lifecycle and the launch
decision exist once.
Pull model. The page asks for snapshot(cursor) ~12 times per second instead of Python pushing at
it: every DOM write stays on the page's own timeline and a late snapshot is a dropped frame, not a stall.
The log has a cursor. A bounded 6000-line ring plus a monotonic counter; the page asks for "everything after N" and, if it fell behind further than the ring, gets the oldest line still held rather than a gap it cannot detect.
Rows read live FileRecords. download_file publishes rec.done_bytes and rec.live_mbs about
four times a second on its own window, kept separate from the stall detector's 60 s history.
Untrusted input. Everything the page sends passes through Engine.apply_cfg(), which coerces and
clamps every number before it reaches a semaphore.
Full write-up: docs/ARCHITECTURE.md.
| Document | What's in it |
|---|---|
| Quick start | install, first run, what each setting does |
| CLI | every flag, exit codes, scripting examples |
| Configuration | every setting, every environment variable, the Chrome profile |
| Providers | how each host is extracted, and how to add another |
| Architecture | how V2 is built, feature by feature |
| Engineering notes | the measurements behind the design decisions |
| Troubleshooting | 403s, Turnstile failures, CDP conflicts, stalls |
| FAQ | why curl_cffi is mandatory, what --browsers means |
| Changelog | every version since 14.0 |
Binds 127.0.0.1 only, on a kernel-chosen port, and every /api/ call must carry the token minted
at startup — without it, 403. The API starts downloads and reads paths, so this is not a formality.
The process exits by itself after 12 s with no requests: the page polls every 80 ms, so "no requests"
means "window closed".
| File | Description |
|---|---|
moontech_*.log |
Human-readable performance report |
moontech_*.json |
Per-file metrics (machine-readable) |
output_links.txt |
Extracted direct links (Links-only mode) |
failed_links.txt |
URLs that failed every retry |
settings.json |
GUI settings, pasted links, language |
Place next to the scripts:
| File | Purpose |
|---|---|
proxies.txt |
Proxy list — ip:port:user:pass or http://user:pass@ip:port |
pytest tests/ -q # engine + CLI: no browser for fuckingfast, exactly one for datanodes
python integration_http.py # browser → loopback HTTP → engine (the path start.bat takes)
python integration_web.py # pywebview path
python render_gui.py out/ # renders at 2554x1400 and 1440x900 + overflow audit
python moon_engine.py # headless engine: prints a snapshot and exitstests/test_no_chrome.py stubs Chrome and the network at the moon_extract boundary, so it needs no browser,
no display and no Playwright install — it runs in CI on every push (.github/workflows/lint.yml).
- OS: Windows 10 / 11 (the GUI needs Edge or Chrome — both ship with Chromium)
- Python: 3.10+
- Disk: ~150 MB for the Playwright Chromium (datanodes only)
- Packages:
aiohttp,playwright,curl_cffi;pywebviewoptional
Made with 🖤 and cold coffee

