首次运行需额外执行 playwright install chromium 下载浏览器;GUI 依赖 Windows 上的 Edge WebView2 运行时;datanodes.to 路径会自动化站点反爬挑战,可能违反该站服务条款。
moondownloader
LeyckerSMIT活跃维护批量下载 datanodes.to 和 fuckingfast.co 网盘文件的工具,通过 CDP 驱动真实 Chrome 或纯 HTTP 模拟 Chrome TLS 指纹绕过反爬,支持流式下载、WebView2 界面和无头 CLI。
Bulk file downloader for datanodes.to and fuckingfast.co — real-Chrome extraction over CDP, pure-HTTP extraction with a Chrome TLS fingerprint, aiohttp streaming, WebView2 GUI and a headless CLI. Windows, Python 3.10+, MIT.
pip install -r requirements.txt
playwright install chromium
python moon_bridge.pyREADME 未说明显存或 GPU 要求;需 Windows 系统、Python 3.10+,GUI 依赖 Edge WebView2,提取需安装 Playwright 的 Chromium。
批量下载 datanodes.to 和 fuckingfast.co 链接的工具,用真实 Chrome 或纯 HTTP 绕过反爬提取直链
批量下载 datanodes.to 和 fuckingfast.co 网盘文件的工具,通过 CDP 驱动真实 Chrome 或纯 HTTP 模拟 Chrome TLS 指纹绕过反爬,支持流式下载、WebView2 界面和无头 CLI。
核心能力
- 用 BrowserGate 按需启动共享 Chrome,纯 HTTP 批次完全不启动浏览器
- fuckingfast 优先走 curl_cffi 纯 HTTP 提取,被拒后才回落到 Chrome
- 用 aiohttp 多流并发下载,默认 8 路下载流
- GUI 基于 Edge WebView2,页面轮询 snapshot 拉取状态
- CLI 与 GUI 共用同一引擎,支持结构化退出码
容易踩的坑
- v4.2v4.2 — the CLI reads the engine's own numbers最新正式版2026-09-09 · 2026-09-09 05:42 UTC
A maintenance release. One change you will see, in the CLI; the rest is accounting and CI. Every entry except the version strings and the Dependabot pins came from an outside contributor.
The one you will see
The CLI's progress line now comes from the engine's own snapshot.
moon_cli.pyused to keep a second copy of the byte-rate arithmetic, the counters and the lock that guarded them, so a speed or ETA fix landed in one front-end and not the other. It now samples the sameEngine.snapshot()the GUI reads. The line names its phase —extracting 12/48while provider pages are being resolved, thendownloading 30/48while files transfer — and a snapshot identical to the last one is not printed again, so a slow extraction no longer fills the terminal with repeated zero-speed lines. A stall-killed URL that is re-extracted cannot advance the count twice, and the final summary and the exit codes introduced in 4.1 read the engine'sokandfail. Documented indocs/CLI.md. Thanks to @breezeFur (#97, #154).The rest
- A completed file's final write buffer was counted twice in
bytes_acc, so any transfer smaller than the 16 MiB write buffer counted exactly double in the Downloaded card, the CLI totals and the ETA's average-file term — a 5 MiB transfer recorded 10 MiB. Nine regression tests now pin every byte to one entry. Thanks to @snowyukitty (#172, #175). - The
Extractorsslider implied a concurrency datanodes never gets. Its pages are capped byPages(1–8), so 32 extractors bought nothing there. The control now shows the effective limit,min(Extractors, Pages), updating from either slider, in both languages. Thanks to @yhuikzdtguioaert (#83, #169). - A CI syntax gate for
web/— a pull request touching onlyweb/used to run no checks at all.node --check web/app.jsnow runs behind aweb/**paths filter. Thanks to @tunglambk (#174, #176). - The two files that control what CI checks were the two files CI did not watch.
ruff.tomlandpytest.iniare now in the workflow filters, anddocs-cli-check.ymlwatches its own file on pull requests too. Thanks to @nightcityblade (#164, #165) and @StefStrg (#93, #168). - A regression test for the
download_filestub, which fails when the engine reaches the real downloader instead of the fixture;run_enginegained amodeparameter so the no-Chrome suite can exercise download mode at all. Thanks to @harshvardhan60792 (#160, #170). - The
E501comment inruff.tomlclaimed line length was enforced. It is not, and the file now says so. Thanks to @FlaggedATX (#79, #171). Engine._LOG_MAX_LINES, defined once and read nowhere, is gone. Thanks to @mazi-eth (#80, #167).- Four places still said 14.x or 4.0 —
SECURITY.md,web/index.html, theapp.jsfallback and demo mode, and the bug-report form. They now say 4.x and v4.2.
Upgrading
Pull, then reinstall if you use the constraints file:
pip install -r requirements.txt -c constraints.txt. The pins moved tocurl_cffi==0.16.2,cffi==2.1.1andgreenlet==3.5.5(Dependabot, #181–#183); the ranges inrequirements.txtare unchanged. No settings changed, the interface is the 3.0 one apart from the new note under theExtractorsslider, and the exit codes are the 4.1 ones. One thing to check: if a script parses the CLI's progress lines, the format changed — the phase is now explicit, and unchanged lines are not repeated.Full detail, with the reasoning behind each fix, is in CHANGELOG.md.
- A completed file's final write buffer was counted twice in
- v4.1v4.1 — written by other people2026-08-12 · 2026-08-12 01:22 UTC
查看更新说明
Every entry in this release came from an outside contributor. Two are bugs you can hit in normal use; the rest make the project harder to break by accident.
Two you can hit
A full destination disk now stops the run.
ENOSPCwas handled like any other transfer error, so the queue kept going and the retry machinery kept re-fetching data that could never be written — on the run that reported it, 46 files each downloading for ~236s before failing, roughly 12 GB pulled off the network and discarded, with nothing on screen saying why. It is now detected byerrnoand treated as a run-level fatal state: intake and retries stop, active transfers unwind at their next write boundary, and the live log names the folder and the shortfall. Interrupted files keep their.tmpand stay resumable, and only the URL that triggered it is recorded as failed. Thanks to @shard872 (#116, #150).Stop interrupts downloads already in flight. It used to set a flag and close Chrome while a transfer that had already started ran to completion — on a large file the button appeared to do nothing for minutes. Each active download's kill event is now signalled directly, and a user-initiated stop is recorded as
stoppedrather than as a stall kill, so the retry path cannot re-queue the file and undo it. The partial.tmpis kept andfailed_links.txtis not written for a run you ended yourself. Thanks to @Allen58562 (#65, #149).The rest
- The CLI reports the run's outcome in its exit code —
0all succeeded,1some failed,3every URL failed,2a pre-flight problem. It previously exited 0 whether one file failed or all of them did. Note that the two pre-flight errors moved from1to2, matching argparse; a script testing for1specifically needs updating. Thanks to @AdvaitVarhade (#32, #153). ruffruns once per pull request instead of once inside each of five Python versions, checking identical work every time. The pinned version is unchanged. Thanks to @nightcityblade (#81, #158).- A test assertion that could not fail — shared by four tests, comparing a fabricated number against itself through two of them. Thanks to @XEDAB (#155, #157).
- The test suite could have made real network requests — one stub sat outside the loop that patches both front-ends, so the engine held the real downloader. Thanks to @XEDAB (#160, #162).
pytest.ini, the project's first pytest configuration, silencing one deprecation by exact message rather than by category. Thanks to @Divesh-Kshirsagar (#151, #161).- The dead
THEMEpalette is gone frommoon_engine.py— residue from when the engine was generated from a tkinter GUI. Thanks to @AashishGupta2007 (#145, #159).
Upgrading
Nothing to do beyond pulling. No new dependencies, no settings changed, and the interface is unchanged since 3.0. The only behaviour change to be aware of is the CLI pre-flight exit code moving from 1 to 2.
Full detail, with the reasoning behind each fix, is in CHANGELOG.md.
- The CLI reports the run's outcome in its exit code —
- v4.0V4 — both providers repaired2026-08-05 · 2026-08-05 20:27 UTC
查看更新说明
Both providers changed how they hand out links and every download stopped. This release is the repair.
Both providers changed the way they hand out links and every download stopped. This release is the repair. The GUI is unchanged since 3.0.
Fixed
- datanodes.to removed step 1. The share URL used to return a form whose
submit carried
name="method_free" value="Free Download >>"; the page now answers with step 2 directly, tokens already minted, and says so on screen ("STEP 2 OF 2"). The extractor waited 22s for#downloadRevealand#method_free, timed out and gave up before ever reaching the trigger chain that was already working — the failing runs are 22.5s per link against a 22.0s budget. It now waits for whichever shape the server sends and branches on the page rather than on an assumption; the old two-step path is still taken when the old markup arrives. Skipping the obsolete POST also matters on its own: a secondPOST /downloadre-runs SecSave and invalidates the token step 2 is holding, so the run was destroying its own session. - The wait now covers the operator's reaction time. On the new shape the
controls only appear once Cloudflare has been answered, and 22s is not a human
budget. It is
max(step-1 budget, manual captcha budget)— the GUI's Captcha slider, 30-600s. - fuckingfast.co put Turnstile in front of
POST /f/<id>/go. The endpoint answers403 captcha verification failedwithout acf-turnstile-responsetoken, which no TLS fingerprint can mint. Ruled out first: it is not the TLS impersonation (the page itself returns 200) and not a missing session (same cookies, and an empty token field, both still 403). There is no official alternative — no API, no docs, no premium tier, no registration, and the bundle exposes only upload endpoints. Links that are refused now fall through to the browser the app already opens for datanodes. A batch fuckingfast still serves over plain HTTPS never opens a window, so the rule thattests/test_no_chrome.pyexists to protect still holds. - The Turnstile auto-click had no target on fuckingfast. The widget locator
looked for
.cf-turnstileas a class and for an iframe whosesrccontainschallenges.cloudflare; fuckingfast uses it as an id and its challenge iframe carries nosrcat all, so both lookups missed and the box was never clicked — visible on screen, sitting there untouched. It now also matches#cf-turnstileand[data-sitekey]. - The fuckingfast page usually has nothing to solve. Its trigger is
click[!!window.turnstileToken || !!window.dlCleared]— note the OR — so waiting for a token waits for something that often never comes. The flow now clicks rather than waits, closes the ad tab the first click opens, and presses again, which is what a person does. Measured: the flag never arms and the click submits anyway. - Per-link browser contexts are gone from that path. The first version gave
each link its own
browser.new_context()— the v14.6 mistake already recorded aboveacquire_lane, where several identities from one IP read as a bot farm and Turnstile answers with a hard failure. It goes through the same pooled context datanodes uses: one profile, one cookie jar, onecf_clearance. - Popup and route handlers are page-scoped. Registering them on the shared context added one more of each per link; after the first extraction the next one stalled. Bound to the page, they die with it. This is what took the sequence from 1/3 to 3/3.
Verified
- datanodes: 5/5 through the CLI end to end, and 2/2 extraction
- fuckingfast: 3/3 consecutive, 6.8-9.7s per link, auto-solve only
(
MOON_DN_CAPTCHA_WAIT=0, so no manual tick was possible) - both providers together: 4/4
pytest tests/ -q: 24 passed
Full changelog: https://github.com/LeyckerS/moondownloader/blob/main/CHANGE
- datanodes.to removed step 1. The share URL used to return a form whose
submit carried
- v3.0V3 — rebuilt interface2026-08-02 · 2026-08-02 00:45 UTC
查看更新说明
The interface was rebuilt. The engine was not: extraction, downloading and the CLI are the same code they were in 2.1.
Added
- A cold open. The mark draws itself on as a vector, the wordmark resolves out of a blur, a raking beam crosses the field, and the card cascade is held — not merely delayed — until it hands over. Under two seconds, ended instantly by any key or click, and removed from the DOM when it finishes.
- The mark is an SVG and it moves. Crescent and orbit are separate shapes, so the ring tilts continuously: the minor radius is what animates, which reads as a rotation in depth. The same drawing now serves the topbar, the cold open and the empty state, and the PNG survives only as the favicon.
- A hero band replaces the four stat cards. The rate is a 49px figure with the plot spanning the card underneath; completed, downloaded and ETA are reference and sit small.
- The settings column is one surface. Five floating cards became four sections of a single panel divided by a hairline, with Start built into the same object.
- Transfer rows carry their provider, read off the source URL, and their weight follows their state: what is moving gets room and a brighter name, what has finished tightens and recedes.
- Filter box and state chips over the transfer list. Presentation only — rows are hidden, and no count the engine owns is recomputed.
- Drag and drop of links or a
.txtanywhere on the window, appended rather than replacing what is already there. - Keyboard shortcuts —
Ctrl+Enter,Ctrl+O,/,?,Esc— with a shortcut panel. - Copy the source link per row, and an end-of-run summary that reads the last snapshot and can copy every failed link.
- Idle motion. The blooms drift, a glint travels the topbar hairline, the wordmark catches a sheen and Start shows one while it waits. All slow, and none of them claiming that anything is happening.
Changed
- One higher-contrast palette. The dimmest text tier moves from
#55677fto#97a9c1and the background photograph is pushed back — a real legibility gain in a bright room. - The speed plot. The head dot leaves the SVG: the plot is drawn with
preserveAspectRatio="none", which was rendering a<circle>as a flat ellipse. The area wash comes down, and a recessive hairline marks the window mean, since a plot scaled to its own peak cannot otherwise tell steady from spiky. It is drawn only when there is signal to average. - View Transitions on the Transfers/Log swap, behind a capability check.
- Scroll-driven edge fades on the scrollers, applied to the edge that actually has content past it; when a list does not overflow there is no mask at all.
- The
prefers-reduced-motionblock is gone, deliberately. Windows reports "reduce" whenever its own animation setting is off, and every version of honouring that here removed the thing the release is for — first the whole design, then the cold open, then the logo orbit and the wordmark sheen. The GUI now animates for everyone. Nothing flashes and every loop is slow, but this is a stated choice rather than an oversight, and a motion preference is the obvious thing to add if anyone asks for one. VERSIONwas stillv2.0after the 2.1 release, so the app under-reported itself for a whole version. It now tracks the tag again, atv3.0.
Full changelog: https://github.com/LeyckerS/moondownloader/blob/main/CHANGELOG.md
- v2.1v2.1 — nine outside contributors2026-08-01 · 2026-08-01 08:36 UTC
查看更新说明
Nine people outside the project shipped changes in this release. Two days ago that number was zero. Everything below came from someone who read the code, disagreed with it, and sent a patch.
Fixed
The elapsed-time clock kept counting after a run finished.
snapshot()computed the elapsed time fromnow - t0unconditionally, so the display carried on ticking after the last file landed. The engine now records the end time at the single point where a run terminates — covering both a normal finish and a crash — and freezes the value there. (#73, @RubenSanosh)A discarded partial download said nothing. When a server answers
200instead of206it is refusing to resume, and the code correctly restarted from zero — silently. A multi-gigabyte transfer appeared to begin again for no reason. The restart is now recorded and appears inmoontech_*.log, with a regression test that fakes the response without touching the network. (#98, @XEDAB)--proxiesfailed silently. A misspelled path, or a file the parser did not recognise, loaded zero proxies and printed nothing — so a run started specifically to avoid direct connections made them anyway, with no indication. (#48, @AdvaitVarhade)The headless CLI example documented a command that never worked.
README.mdshowed positional URLs the parser does not accept and a-oflag that does not exist. (#53, @tomatotomata)The guard that should have caught it could not see it.
tests/test_docs_cli_flags.pymatched only--longflags, so the bogus-opassed every check. It now validates single-dash flags against the parser too. (#104, @kocaemre)docs/ARCHITECTURE.mdstill described the download engine as two copies, which #41 had made untrue. (#68, @Moferanoluwa)Changed
The download engine exists once.
download_file,TelemetryandProxyPoolmoved intomoon_download.py; both front-ends import them instead of each carrying a copy. A fix in the download path is now a one-file change, and the two copies can no longer drift apart. (#41, @pollychen-lab)Exception handling says why. Handlers across
moon_bridge.py,moon_cli.py,moon_download.py,moon_engine.pyand the first slice ofmoon_extract.pyare narrowed where the exception set is knowable, and where they must stay broad they now state a specific reason.moon_extract.pykeeps its deliberate deferred playwright import, so a fuckingfast-only batch still never loads it. (#54, #71, #82 — @AdvaitVarhade, @Moferanoluwa)Added
CI runs on Python 3.10, 3.11 and 3.12 and lints with
ruff. The ignore list parks the rules that fire on the codebase as it stands, each with its count and the reason — a baseline that catches regressions rather than a reformat. (#69, @Moferanoluwa)Documentation-only pull requests are checked against the real CLI parser. They previously ran no checks at all. A new workflow reads the true flag set from
moon_cli.py --helpat test time and fails on any documented invocation using a flag the parser does not accept. Reading--helprather than hardcoding means adding a flag never requires touching the test. (#70, @Moferanoluwa)The byte-compile list cannot drift. The job named "Byte-compile every module" compiled a hand-written list that had fallen behind —
moon_download.py, the shared download engine, was never compiled at all. It now derives the list fromgit ls-files, and the workflow watches its own file. (#77, @darlenepolek)The verification suite is a pytest suite. (#38, @pollychen-lab)
moon_cli.py --version, recorded in both report flavours, from oneVERSIONconstant. (#61, @Moferanoluwa)docs/CLI.md— every flag, its default, and what it actually controls. (#37, @kushin25)Contributors
@kushin25 the CLI reference @pollychen-lab the pytest suite, and the shared download engi
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
🌙 Moon Downloader
V4.2
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
Contributions welcome — the roadmap ranks everything open by size and by whether it needs Windows. Most of it doesn't. Three issues are held for anyone who has never had a pull request merged anywhere.
📸 The interface
The rate is the figure being watched, so it gets the left side and the plot underneath; completed, downloaded and ETA are reference and sit small. Every row shows which host it came from. Rendered from the page's own mock engine — the numbers are synthetic, which is what the demo chip in the corner says.
The cold open: the mark draws itself on, the wordmark resolves out of a blur, and the card cascade is held until it hands over. Under two seconds, and any key or click ends it.
Filter box and state chips over the list; what is moving gets room and a brighter name, what has finished tightens and recedes.
🔧 Engineering highlights
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.
🌗 What V4.2 is
A maintenance release. The change you will notice most is in the CLI: its progress line now comes
from the same Engine.snapshot() the GUI reads, names its phase — extracting 12/48, then
downloading 30/48 — and stops repeating itself while a slow extraction runs. The rest is accounting
and CI, from outside contributors: a final write buffer that was counted twice in the byte totals, an
Extractors slider that implied a concurrency datanodes never gets (it now shows the real limit), a
syntax check for web/ where a pull request used to merge green with no checks at all, and two CI
configuration files that CI itself did not watch — plus, from the maintainer, the last four places
that still said 14.x or 4.0. Full detail and credits in CHANGELOG.md.
🌘 What V4.1 was
A maintenance release written entirely by other people — every entry in its changelog came from an outside contributor. Two were bugs you could hit in normal use:
- A full destination disk stops the run.
ENOSPCused to be handled like any other transfer error, so the queue kept going and the retry machinery kept re-fetching data that could never be written — 46 files and roughly 12 GB pulled and discarded on the run that reported it, with nothing on screen explaining why. It is now detected byerrno, aborts the run, names the folder and the shortfall in the live log, and leaves every.tmpresumable. - Stop interrupts transfers already in flight. It used to set a flag and close Chrome while an in-progress download ran to completion, so on a large file the button appeared to do nothing for minutes.
The rest made the project harder to break by accident: structured CLI exit codes so a script can tell success from partial from total failure, a linter that runs once per pull request instead of five times, a test assertion that could never fail, and a test stub that left the engine able to reach the real network. Full detail and credits in CHANGELOG.md.
🌘 What V4 was
Both providers broke, and that release was the repair. The interface is unchanged since V3.
- datanodes removed step 1. The share URL used to return a form whose submit carried
name="method_free"; it now answers with step 2 directly, tokens already minted, and says "STEP 2 OF 2" on screen. The extractor waited 22s for markup that no longer exists and gave up before reaching the trigger chain that was already working. - fuckingfast added Turnstile to
POST /f/<id>/go, so the pure-HTTP path returns 403 on its own.
Both are fixed and measured: datanodes 5/5 end to end, fuckingfast 3/3 consecutive at 6.8–9.7s with auto-solve only, both together 4/4. The full account — including three defects introduced while building the fuckingfast path and caught in testing — is in CHANGELOG.md.
🖥️ What V3 was
The interface, rebuilt. The engine is untouched — extraction, downloading and the CLI are the same code they were in 2.1, and that is deliberate: this release changes what you look at, not what it does.
| before V3 | V3 | |
|---|---|---|
| Launch | the window simply appeared | a cold open under two seconds, skippable, removed from the DOM when done |
| Mark | a 512px PNG | an SVG whose crescent and orbit move independently |
| Headline | four equal stat cards | one hero band; the rate is a 49px figure, the rest is reference |
| Settings column | five floating cards | one surface, hairline sections, Start built in |
| Transfer rows | all identical weight | provider chip per row, weight follows state |
| Finding a file | scrolling | filter box, state chips, / to focus |
| Getting links in | the Load button | drag and drop anywhere, appended not replacing |
| After a run | the list, as it was | a summary that can copy every failed link |
Keyboard: Ctrl+Enter start or stop · Ctrl+O load a .txt · / filter · ? the shortcut panel ·
Esc close or clear.
On motion. The GUI animates for everyone; there is no prefers-reduced-motion exception, and
that is a stated choice rather than an oversight. Nothing flashes and every loop is slow. If you
want a motion preference, open an issue —
it is a small change.
⚡ How the two providers differ
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 first, the shared Chrome when refused |
| Browser | yes — pages on one window, one identity | only for links /go refuses |
| Captcha | Turnstile: auto-solve, manual fallback | Turnstile since Aug 2026, same auto-solve |
| Cost per link | seconds | ~0.25 s over HTTPS, ~7 s through the browser |
| Settings | Pages, Captcha wait, Chrome path, API key | nothing to tune |
This changed in V4. fuckingfast put Cloudflare Turnstile in front of
POST /f/<id>/go in August 2026: without a cf-turnstile-response token the endpoint answers
403 captcha verification failed, and no TLS fingerprint can mint one. Links it refuses now go
through the same Chrome datanodes uses. A batch fuckingfast still serves over plain HTTPS opens no
window at all — the HTTP path is tried first, every time, and the browser is only reached for after
a refusal.
A batch that contains one datanodes link opens exactly one shared Chrome, on demand, no matter how many extractors are running.
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.
⚖️ Scope and responsible use
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.
🚀 Quick start
One-click (Windows)
- 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.
Manual
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 --urls links.txt --output ./downloads # headless CLI
Just want to look at the interface?
Open web/index.html in Chrome or Edge. It boots in demo mode with a synthetic engine.
🖥️ GUI features
- 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)
⚙️ Settings
| 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.
🏗️ Architecture
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.
📚 Documentation
| 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 the engine 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 --extractors means |
| Contributing | architecture, what counts as a contribution, the verification commands |
| Changelog | every version since 14.0 |
🤝 Contributing
→ The roadmap — everything open, ranked by how hard it is and whether it needs Windows.
Most of the open work does not require a Windows machine. Documentation, CI, tests and dependency
work all run on Linux and macOS, and the test suite stubs Chrome and the network at the moon_extract
boundary, so it runs anywhere. Each issue says up front which it is.
| good first issue | scoped small, with the files to touch and the acceptance criteria already written out |
| help wanted | everything open to outside contributors, including the larger items |
Claim one by commenting on it in your own words — no need to ask permission first. What gets a pull request merged, and what gets one sent back, is written out in CONTRIBUTING.md.
How review works here, so you know what to expect:
- Every claim in a pull request description is checked against the function it describes, not taken
on trust. Where a review says something is wrong, it cites
file.py:lineand says whether it blocks or is merely untidy. - Issue bodies are written by the maintainer and have been wrong. If the code disagrees with the issue, the code wins — #63 was merged partly because it corrected the issue that opened it, and corrections to a specification are made publicly on the thread where the mistake happened.
- Keeping a diff to its issue matters more than its size. Reformatting and "while I was in there" edits are the most common reason a pull request needs a second round.
Everyone who has shipped a change is named in AUTHORS.md with a line describing what they actually did, added in the same session as the merge.
🔒 The local server
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".
📂 Output files
| 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 |
📁 Optional files
Place next to the scripts:
| File | Purpose |
|---|---|
proxies.txt | Proxy list — ip:port:user:pass or http://user:pass@ip:port. Downloads only — see below |
Proxies wrap the download session and nothing else. Link extraction always connects
directly, from your own address: the shared Chrome instance datanodes needs for its
Turnstile challenge, and the curl_cffi session fuckingfast uses, are never routed
through the pool. An unreachable proxy list therefore stalls downloads while pages keep
opening normally — that is the design, not a broken proxy.
✅ Verification
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 exits
68 tests, and they need no browser, no display and no Playwright install: Chrome and the network are
stubbed at the moon_extract boundary, so the suite runs anywhere. CI byte-compiles every tracked
Python file on 3.10 through 3.14, runs ruff against a pinned version, checks that every runtime
dependency carries an upper bound, and runs the suite — on every push and every pull request
(.github/workflows/lint.yml).
📋 Requirements
- 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