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.

v4.2发布于 2026-09-09 05:42 UTC1,599star58fork同步于 2026-11-07 07:34 UTC

怎么上手

由 README 提炼,执行前请确认
pip install -r requirements.txt
playwright install chromium
python moon_bridge.py
硬件要求按 README 说明

README 未说明显存或 GPU 要求;需 Windows 系统、Python 3.10+,GUI 依赖 Edge WebView2,提取需安装 Playwright 的 Chromium。

下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

批量下载 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 共用同一引擎,支持结构化退出码

容易踩的坑

首次运行需额外执行 playwright install chromium 下载浏览器;GUI 依赖 Windows 上的 Edge WebView2 运行时;datanodes.to 路径会自动化站点反爬挑战,可能违反该站服务条款。

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v4.2
  1. 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.py used 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 same Engine.snapshot() the GUI reads. The line names its phase — extracting 12/48 while provider pages are being resolved, then downloading 30/48 while 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's ok and fail. Documented in docs/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 Extractors slider implied a concurrency datanodes never gets. Its pages are capped by Pages (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 only web/ used to run no checks at all. node --check web/app.js now runs behind a web/** paths filter. Thanks to @tunglambk (#174, #176).
    • The two files that control what CI checks were the two files CI did not watch. ruff.toml and pytest.ini are now in the workflow filters, and docs-cli-check.yml watches its own file on pull requests too. Thanks to @nightcityblade (#164, #165) and @StefStrg (#93, #168).
    • A regression test for the download_file stub, which fails when the engine reaches the real downloader instead of the fixture; run_engine gained a mode parameter so the no-Chrome suite can exercise download mode at all. Thanks to @harshvardhan60792 (#160, #170).
    • The E501 comment in ruff.toml claimed 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, the app.js fallback 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 to curl_cffi==0.16.2, cffi==2.1.1 and greenlet==3.5.5 (Dependabot, #181–#183); the ranges in requirements.txt are unchanged. No settings changed, the interface is the 3.0 one apart from the new note under the Extractors slider, 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.

  2. 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. ENOSPC was 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 by errno and 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 .tmp and 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 stopped rather than as a stall kill, so the retry path cannot re-queue the file and undo it. The partial .tmp is kept and failed_links.txt is 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 — 0 all succeeded, 1 some failed, 3 every URL failed, 2 a pre-flight problem. It previously exited 0 whether one file failed or all of them did. Note that the two pre-flight errors moved from 1 to 2, matching argparse; a script testing for 1 specifically needs updating. Thanks to @AdvaitVarhade (#32, #153).
    • ruff runs 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 THEME palette is gone from moon_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.

  3. 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 #downloadReveal and #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 second POST /download re-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 answers 403 captcha verification failed without a cf-turnstile-response token, 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 that tests/test_no_chrome.py exists to protect still holds.
    • The Turnstile auto-click had no target on fuckingfast. The widget locator looked for .cf-turnstile as a class and for an iframe whose src contains challenges.cloudflare; fuckingfast uses it as an id and its challenge iframe carries no src at all, so both lookups missed and the box was never clicked — visible on screen, sitting there untouched. It now also matches #cf-turnstile and [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 above acquire_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, one cf_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

  4. 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 .txt anywhere 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 #55677f to #97a9c1 and 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-motion block 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.
    • VERSION was still v2.0 after the 2.1 release, so the app under-reported itself for a whole version. It now tracks the tag again, at v3.0.

    Full changelog: https://github.com/LeyckerS/moondownloader/blob/main/CHANGELOG.md

  5. 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 from now - t0 unconditionally, 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 200 instead of 206 it 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 in moontech_*.log, with a regression test that fakes the response without touching the network. (#98, @XEDAB)

    --proxies failed 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.md showed positional URLs the parser does not accept and a -o flag that does not exist. (#53, @tomatotomata)

    The guard that should have caught it could not see it. tests/test_docs_cli_flags.py matched only --long flags, so the bogus -o passed every check. It now validates single-dash flags against the parser too. (#104, @kocaemre)

    docs/ARCHITECTURE.md still described the download engine as two copies, which #41 had made untrue. (#68, @Moferanoluwa)

    Changed

    The download engine exists once. download_file, Telemetry and ProxyPool moved into moon_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.py and the first slice of moon_extract.py are narrowed where the exception set is knowable, and where they must stay broad they now state a specific reason. moon_extract.py keeps 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 --help at test time and fails on any documented invocation using a flag the parser does not accept. Reading --help rather 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 from git 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 one VERSION constant. (#61, @Moferanoluwa)

    docs/CLI.md — every flag, its default, and what it actually controls. (#37, @kushin25)

    Contributors

    @kushin25the CLI reference
    @pollychen-labthe pytest suite, and the shared download engi

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    🌙 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

    Python Playwright WebView2 License CI CodeTriage


    Best tested: ~250 MB/s on 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. ENOSPC used 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 by errno, aborts the run, names the folder and the shortfall in the live log, and leaves every .tmp resumable.
    • 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 V3V3
    Launchthe window simply appeareda cold open under two seconds, skippable, removed from the DOM when done
    Marka 512px PNGan SVG whose crescent and orbit move independently
    Headlinefour equal stat cardsone hero band; the rate is a 49px figure, the rest is reference
    Settings columnfive floating cardsone surface, hairline sections, Start built in
    Transfer rowsall identical weightprovider chip per row, weight follows state
    Finding a filescrollingfilter box, state chips, / to focus
    Getting links inthe Load buttondrag and drop anywhere, appended not replacing
    After a runthe list, as it wasa 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.tofuckingfast.co
    Extractionreal Chrome over CDP + persistent profileplain HTTPS first, the shared Chrome when refused
    Browseryes — pages on one window, one identityonly for links /go refuses
    CaptchaTurnstile: auto-solve, manual fallbackTurnstile since Aug 2026, same auto-solve
    Cost per linkseconds~0.25 s over HTTPS, ~7 s through the browser
    SettingsPages, Captcha wait, Chrome path, API keynothing 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.to path 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)

    1. Install Python 3.10+ — check ✅ "Add Python to PATH"
    2. Double-click start.bat
    3. 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 / others split
    • 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

    SettingRangeDefaultApplies toDescription
    Extractors2 – 3216bothParallel extraction workers
    DL streams2 – 4848bothConcurrent download connections
    Retries0 – 53bothExtraction retries per URL (network retries are separate)
    Pages1 – 88datanodesTabs on the shared Chrome window — not separate windows
    Captcha30 – 600 s30datanodesManual Turnstile wait
    Chromepathautodetectdatanodeschrome.exe to drive over CDP
    API keystring—datanodesPremium 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

    DocumentWhat's in it
    Quick startinstall, first run, what each setting does
    CLIevery flag, exit codes, scripting examples
    Configurationevery setting, every environment variable, the Chrome profile
    Providershow each host is extracted, and how to add another
    Architecturehow the engine is built, feature by feature
    Engineering notesthe measurements behind the design decisions
    Troubleshooting403s, Turnstile failures, CDP conflicts, stalls
    FAQwhy curl_cffi is mandatory, what --extractors means
    Contributingarchitecture, what counts as a contribution, the verification commands
    Changelogevery 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 issuescoped small, with the files to touch and the acceptance criteria already written out
    help wantedeverything 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:line and 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

    FileDescription
    moontech_*.logHuman-readable performance report
    moontech_*.jsonPer-file metrics (machine-readable)
    output_links.txtExtracted direct links (Links-only mode)
    failed_links.txtURLs that failed every retry
    settings.jsonGUI settings, pasted links, language

    📁 Optional files

    Place next to the scripts:

    FilePurpose
    proxies.txtProxy 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; pywebview optional

    Made with 🖤 and cold coffee