[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-105305":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":17,"stars7d":17,"stars30d":17,"stars90d":16,"forks30d":16,"starsTrendScore":18,"compositeScore":19,"rankGlobal":10,"rankLanguage":10,"license":20,"archived":21,"fork":21,"defaultBranch":22,"hasWiki":23,"hasPages":23,"topics":24,"createdAt":10,"pushedAt":10,"updatedAt":33,"readmeContent":34,"aiSummary":10,"trendingCount":16,"starSnapshotCount":16,"syncStatus":14,"lastSyncTime":35,"discoverSource":36},105305,"ff-ruyijs","cutetailbud\u002Fff-ruyijs","cutetailbud","Run any site JavaScript from Python with the real Firefox engine. Headless SpiderMonkey + Necko for automation and scraping.","https:\u002F\u002Fcutetailbud.github.io\u002Fff-ruyijs\u002F",null,"C++",340,222,2,3,0,81,243,91.08,"Other",false,"main",true,[25,26,27,28,29,30,31,32],"anti-bot","automation","firefox","headless-browser","javascript","python","spidermonkey","web-scraping","2026-11-01 00:13:57","\u003Cdiv align=\"center\">\n\n# 🦊 ruyiJS\n\n### Run any site's JavaScript from Python — with the real Firefox engine.\n\n**A new-generation, minimalist browser purpose-built for AI-driven automation.**\u003Cbr>\nThe authentic Firefox JavaScript engine and networking stack, with no window and no renderer —\u003Cbr>\nmade for web scraping, JS reverse engineering, data collection, and getting past bot checks.\n\n[![PyPI](https:\u002F\u002Fimg.shields.io\u002Fpypi\u002Fv\u002Fruyijs?label=pypi&color=111111&logo=pypi&logoColor=white)](https:\u002F\u002Fpypi.org\u002Fproject\u002Fruyijs\u002F) [![Python](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FPython-%E2%89%A53.10-111111?logo=python&logoColor=white)](https:\u002F\u002Fpypi.org\u002Fproject\u002Fruyijs\u002F) [![License](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Flicense-AGPL--3.0%20%2B%20commercial-111111)](LICENSE) [![Windows](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FWindows%20x64-0078D4?logo=windows&logoColor=white)](https:\u002F\u002Fpypi.org\u002Fproject\u002Fruyijs\u002F) [![LinuxDO](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FLinuxDO-community-FFB003?logo=discourse&logoColor=white)](https:\u002F\u002Flinux.do\u002Ft\u002Ftopic\u002F3078486)\n\n\u003Csub>Sponsored by\u003C\u002Fsub>\n\n\u003Ca href=\"https:\u002F\u002Fwww.fastaitoken.com\">\u003Cimg src=\"assets\u002Fsponsor-fastaitoken.png\" height=\"42\" alt=\"FastAIToken\">\u003C\u002Fa>\n\n\u003Csub>Contributors\u003C\u002Fsub>\n\n\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FFacetomyself\" title=\"Facetomyself · #4 macOS arm64 shell, #6 QUIC profiles\">\u003Cimg src=\"assets\u002Fcontributors\u002FFacetomyself.png\" width=\"48\" height=\"48\" alt=\"Facetomyself\">\u003C\u002Fa>&nbsp;&nbsp;\u003Ca href=\"https:\u002F\u002Fgithub.com\u002FStarktomy\" title=\"Starktomy · #5 Linux runtime\">\u003Cimg src=\"assets\u002Fcontributors\u002FStarktomy.png\" width=\"48\" height=\"48\" alt=\"Starktomy\">\u003C\u002Fa>\n\n**[Install](#-installation)** &nbsp;·&nbsp; **[Features](#-features)** &nbsp;·&nbsp; **[Verification systems](#-verification-systems-it-defeats)** &nbsp;·&nbsp; **[Examples](#-examples)** &nbsp;·&nbsp; **[FAQ](#-faq--troubleshooting)** &nbsp;·&nbsp; **[Contact](#-contact)** &nbsp;·&nbsp; **[LinuxDO](https:\u002F\u002Flinux.do\u002Ft\u002Ftopic\u002F3078486)** &nbsp;·&nbsp; **English** · [简体中文](README.zh-CN.md)\n\n> **⚠️ Disclaimer:** This project is for researching next-generation AI browsers. Any form of brute-force reverse engineering is strictly rejected. The source code is open source under the AGPL-3.0; closed-source or proprietary use requires a commercial license.\n\n\u003Cbr>\n\n\u003Cpicture>\n  \u003Csource media=\"(prefers-color-scheme: dark)\" srcset=\"assets\u002Farchitecture-dark.svg\">\n  \u003Cimg src=\"assets\u002Farchitecture.svg\" width=\"880\" alt=\"ruyiJS architecture: your Python code drives a ruyiJS Context — an isolated process holding the SpiderMonkey JS engine, 38 browser contexts, and the Necko network stack — which talks to the target site.\">\n\u003C\u002Fpicture>\n\n\u003C\u002Fdiv>\n\n\u003Cbr>\n\n## Is ruyiJS for you?\n\nHeadless Chrome and Playwright hand you a whole browser: a GPU-rendering, window-managing, memory-hungry process that anti-bot vendors have spent years learning to detect. ruyiJS takes the opposite path — it embeds only the Firefox internals that matter for automation and leaves the rest behind:\n\n- **The real engine, not an impersonation.** Genuine SpiderMonkey (Firefox 159) and the Necko network stack execute a site's JavaScript, challenges, signatures, and fingerprints exactly as Firefox would.\n- **Truly headless.** No window, no layout, no renderer. A pure JavaScript + Web API environment that starts fast and stays light.\n- **Isolated and reproducible.** Each `Context` is its own process with a persistent JavaScript world and its own proxy, so sessions never leak into each other.\n- **Offline when you need it.** Serve scripts and responses yourself and replay a whole captured flow with no network.\n\nIf you scrape, reverse-engineer web JavaScript, or collect data behind a challenge, it's for you. If a plain HTTP client already gets your pages, you don't need it.\n\nWindows x64 ships as a wheel with **Python 3.10+** (verified on Python 3.14). macOS arm64 is a source build and a fork-release wheel; the donor layout and the surfaces that are still empty are in [docs\u002Fmacos.md](docs\u002Fmacos.md).\n\n## 📦 Installation\n\n```sh\npython -m pip install ruyijs\n```\n\n**Requirements:** Python 3.10+ on Windows x64 (64-bit). Verified on Python 3.10 – 3.14.\n\nThe Windows wheel ships the SHA256-pinned SpiderMonkey + Necko runtime (~42 MB). Local scripts run offline right after install — no browser download, no external dependencies.\n\n**macOS arm64** is not this PyPI wheel. Build it from source with Python 3.12 and two official Firefox donor trees that stay outside the repository, or install the `macosx_26_0_arm64` wheel from the maintainer fork release `v0.2.1`. That wheel is not on PyPI. The page path checked this round is direct HTTPS over HTTP\u002F2. HTTP\u002F1.1 is written when ALPN is `http\u002F1.1` and was unit-tested, not opened against a live origin. Steps and the unfinished surfaces are in [docs\u002Fmacos.md](docs\u002Fmacos.md).\n\nTo upgrade:\n```sh\npython -m pip install --upgrade ruyijs\n```\n\nTo verify the installation:\n```python\nimport ruyijs\nprint(ruyijs.__version__)   # 0.2.1\n```\n\n## 🚀 Quick start\n\n**1 · Open a context and run JavaScript.** Variables and functions persist across `eval` calls within the same context.\n\n```python\nfrom ruyijs import Context\n\nwith Context() as context:\n    context.eval(\"let count = 40\")\n    print(context.eval(\"count += 2\"))        # 42\n    print(context.eval(\"navigator.userAgent\"))\n```\n\n**3 · Point it at a real target.** Configure the environment, load the page, and let the site's own JavaScript mint whatever token it demands.\n\n```python\nwith Context(proxy=\"http:\u002F\u002F127.0.0.1:7890\") as context:\n    context.configure_fingerprint({\n        \"navigator\": {\"language\": \"zh-CN\", \"hardwareConcurrency\": 8},\n        \"locale\": {\"timezone\": \"Asia\u002FShanghai\"},\n    })\n    context.load_page(\"https:\u002F\u002Ftarget.example\u002F\", html)\n    token = context.eval(\"window.__sign(payload)\")   # run the page's signer\n    print(token)\n```\n\n## 🏗️ How it fits together\n\n```\nPython  ──►  ruyiJS Context (isolated process)  ──►  Target site\n                 ├─ SpiderMonkey — Firefox 159 JS engine\n                 ├─ 38 browser contexts — DOM · Canvas · Workers · iframes · …\n                 ├─ Skia — Firefox 159 2D graphics behind Canvas\n                 └─ Necko — HTTP\u002F1.1 · 2 · 3, per-context proxy\n```\n\nYour Python code drives a `Context`. Inside it, the genuine SpiderMonkey engine runs the page's scripts against a real DOM and 38 bounded browser subsystems, while in-page `fetch`\u002FXHR ride the Necko transport. The implementation lives under [`src\u002F`](src) (domain, application, and every browser subsystem) and [`python\u002Fruyijs\u002F`](python\u002Fruyijs) (the binding).\n\n## ✨ Features\n\n### Run a site's JavaScript\n\nExecute scripts in a persistent context, load real HTML with iframes and classic scripts, and query the resulting DOM. References to live JS objects come back as `JSObject` \u002F `JSFunction` you can call from Python.\n\n| Capability | How |\n| :-- | :-- |\n| Execute JS, keep state | `context.eval(src)` — variables and functions persist |\n| Get a callable back | `context.eval(src, to_py=False)` → `JSFunction` |\n| Load a page | `context.load_page(url, html)` — relative URLs resolve against `url` |\n| Top-level navigation | `context.navigate(url)` — no Referer, `Sec-Fetch-Site: none` |\n| Query the DOM | `context.eval(\"document.querySelector('#x').textContent\")` |\n\n```python\nwith Context() as context:\n    context.set(\"config\", {\"offset\": 2})\n    add = context.eval(\"(value) => value + config.offset\", to_py=False)\n    print(add(40))  # 42\n```\n\n### Configure the browser environment\n\nSet Navigator, Screen, viewport, timezone, and locale through one validated profile surface. Commit it before the first page script runs.\n\n```python\nwith Context() as context:\n    context.configure_fingerprint({\n        \"navigator\": {\"language\": \"zh-CN\", \"hardwareConcurrency\": 8},\n        \"screen\": {\"width\": 1920, \"height\": 1080},\n        \"viewport\": {\"width\": 1280, \"height\": 720},\n        \"locale\": {\"timezone\": \"Asia\u002FShanghai\"},\n    })\n    print(context.eval(\"[navigator.language, screen.width]\"))  # ['zh-CN', 1920]\n```\n\n### Native network over Necko\n\nIn-page `fetch` and XHR ride real HTTP\u002F1.1, 2, and 3, through a proxy scoped to the context. Serve responses yourself with `put_resource()` to replay an entire captured flow offline. Inspect every request with `net_log()`. On the macOS source build, the page path checked this round is direct HTTPS over HTTP\u002F2. The boundary is in `docs\u002Fmacos.md`.\n\n### Native TLS fingerprint — switchable across Firefox 100 – 159\n\nThe network stack is Firefox's own **Necko + NSS** (the same TLS library real Firefox uses). The TLS ClientHello — cipher suites, extensions, ALPN, key shares, and their ordering — is produced by the real NSS library, not an emulation, so bot-detection systems that inspect TLS cannot distinguish ruyiJS traffic from a genuine Firefox session.\n\nThe Necko transport can **freely switch between Firefox 100 – 159 TLS profiles**. Each profile reproduces that version's exact ClientHello, giving you a different JA3 \u002F JA4 fingerprint on the wire — useful for rotating browser identities, evading per-version blocks, or testing how a site reacts to different Firefox releases.\n\nChoose the release per context with `tls=` (`\"100\"`–`\"159\"`; default 159). On Windows this sets the TLS ClientHello, HTTP\u002F2 settings and HTTP\u002F3 priorities of that context's Necko transport; set a matching user agent with `configure_fingerprint` when the page should agree. On macOS, direct HTTPS uses arm64 `libnecko_core.dylib` when that library reports NSS 3.131. The shell prefers `dist\u002Fnecko_tls\u002Flibnecko_core.dylib`, then the staged `dist\u002Fnecko` copy. An existing `NECKO_CORE_LIBRARY` wins. `Context()` and `Context(tls=\"100\")` both opened `https:\u002F\u002Fexample.com\u002F` at status 200 and protocol `h2`. The measured handshake was 15 ciphers, 5 groups, and 3 key shares for 159, and 17, 6, and 2 for 100. Library GETs at the cluster boundaries 100, 119, 132, 135, 151, 154, 156, and 159 also returned 200 and `h2`, and the counts matched the profile table. The other versions were not each opened as a page. Omitted request headers and the HTTP\u002F2 settings follow the 0.2.1 product table for that `tls=` value. A header the caller sets wins. HTTP\u002F3 fields in that table are unused, and HTTP\u002F3 is unchanged. A proxy, a missing library, or a different NSS build stays on the donor xpcshell. The proxy was not retested in this run. Plain HTTP still returns `necko 804b0033` from that donor (`docs\u002Ftls-profile.md`).\n\n```python\nwith Context(proxy=\"http:\u002F\u002F127.0.0.1:7890\", timeout=30, tls=\"120\") as context:\n    context.load_page(\"https:\u002F\u002Fexample.com\u002F\", \"\")\n    context.eval(\"globalThis.r = null; fetch('\u002Fapi').then(x => x.json()).then(v => r = v)\")\n    context.advance(25)               # drive the event loop until it settles\n    print(context.eval(\"r\"))\n```\n\n### Canvas on Firefox's native Skia\n\nCanvas 2D draws through Firefox's own official **Skia** (`gfx\u002Fskia` from the Firefox 159 release), not a re-implementation.\n\n### Python ↔ JavaScript bridge\n\nPass data in with `set()`, get callable JS functions out, and expose Python functions to the running script. Workers, Service Workers, and iframes are included.\n\n```python\nwith Context() as context:\n    context.expose(lambda a, b: a + b, \"add\")\n    print(context.eval(\"add(20, 22)\"))  # 42\n```\n\n### Deterministic clock\n\nDrive timers with a logical clock for perfectly reproducible runs, or use the system clock for live work. The host advances time explicitly.\n\n```python\nwith Context() as context:\n    context.configure_clock(\"logical\", epoch_ms=1700000000000)\n    context.eval(\"globalThis.result = 0; setTimeout(() => { result = 42 }, 10)\")\n    context.advance(10)\n    print(context.eval(\"result\"))  # 42\n```\n\n### Errors and logs\n\nJavaScript exceptions surface as `JSError` with name, message, and stack. `console_log()` and `net_log()` return the captured console and network records.\n\n```python\nfrom ruyijs import Context, JSError\n\nwith Context(timeout=10) as context:\n    try:\n        context.eval(\"throw new TypeError('bad input')\", filename=\"demo.js\")\n    except JSError as error:\n        print(error.name, error.message)  # TypeError bad input\n        print(error.stack)\n```\n\n## 🛡️ Verification systems it defeats\n\nEach example drives the site's own JavaScript to produce the parameters it demands, then completes the flow end to end. All ship runnable in the package: `python -m ruyijs.examples \u003Cname>`.\n\n| System | Category | Example |\n| :-- | :-- | :-- |\n| **Akamai Bot Manager 3.x** | sensor_data + sbsd challenge | `akamai` |\n| **DataDome** | Interstitial device-check (plv2 + plv3) | `datadome` |\n| **CreepJS** | Fingerprint audit — passes every section | `creepjs` |\n\n> 🔒 **Strictly prohibited for any unlawful use.** ruyiJS is provided **solely for research into a new generation of AI browsers.** Live results depend on current network conditions and each site's present responses.\n\n## 🧪 Examples\n\nExamples ship inside the package — no need to clone the repo:\n\n```sh\npython -m ruyijs.examples                      # list every example and its install path\npython -m ruyijs.examples akamai               # Akamai Bot Manager 3.x\npython -m ruyijs.examples datadome             # DataDome interstitial device-check\npython -m ruyijs.examples creepjs              # CreepJS fingerprint audit\n```\n\nEvery example supports `--direct` (direct connection) and `--proxy http:\u002F\u002F127.0.0.1:7890`. Add `--help` after a name for all options, and `--output result.json` to save results. Each example directory ships its own README.\n\n## 📚 Documentation & API\n\n| | |\n| :-- | :-- |\n| **`Context.eval` \u002F `set` \u002F `expose`** | Execute JS, pass data in, expose Python callbacks |\n| **`load_page` \u002F `navigate` \u002F `put_resource`** | Load HTML, top-level navigation, serve resources offline |\n| **`configure_fingerprint` \u002F `configure_environment`** | Navigator, Screen, viewport, timezone, locale |\n| **`configure_clock` \u002F `advance` \u002F `tick`** | Logical vs. system clock, drive the event loop |\n| **`JSError` \u002F `console_log` \u002F `net_log`** | Errors as exceptions, console and network records |\n| **`JSObject` \u002F `JSFunction` \u002F `get_runtime_path`** | Live JS handles, bundled-runtime path |\n\n## 📏 Scope & limitations\n\nruyiJS provides JavaScript execution and a broad Web API environment on **Windows x64**, and on **macOS arm64** as a source build or a fork-release wheel. It has **no browser window and no full page layout or rendering** — it is a tool for automation, analysis, and research. Use it in compliance with the terms of service of any site you interact with and with applicable law. The macOS comparison with Windows and the follow-up plan are in [docs\u002Fmacos.md](docs\u002Fmacos.md).\n\n**Related:** [**ruyiHttp**](https:\u002F\u002Fgitee.com\u002Flosenine888\u002FruyiHttp) — a Requests-style Python HTTP library built on Firefox's native networking components.\n\n## ❓ FAQ \u002F Troubleshooting\n\n\u003Cdetails>\n\u003Csummary>\u003Cb>What Python versions are supported?\u003C\u002Fb>\u003C\u002Fsummary>\n\nPython 3.10 and above on Windows x64 (64-bit), tested up to Python 3.14. The macOS source build uses Python 3.12 for the Necko helper.\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>\u003Cb>Does it work on macOS or Linux?\u003C\u002Fb>\u003C\u002Fsummary>\n\nmacOS arm64 is supported as a source build and as a `macosx_26_0_arm64` wheel. That wheel is not uploaded to PyPI. The maintainer fork release `v0.2.1` carries the wheel and `ruyijs-runtime-macos-arm64.zip`. The Windows comparison and the follow-up plan are in [docs\u002Fmacos.md](docs\u002Fmacos.md). Linux is not supported.\n\nThe JS engine is Mozilla's official **SpiderMonkey** and the networking stack is Mozilla's official **Necko** — both are built from the Firefox source tree and kept outside this repository.\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>\u003Cb>Do I need to download a browser?\u003C\u002Fb>\u003C\u002Fsummary>\n\nOn Windows, no. The wheel bundles the pinned SpiderMonkey + Necko runtime, and `pip install ruyijs` runs offline. On macOS, build the two donor trees once; `setup.py` can then make a local wheel that finds `dist\u002Fjs` and `dist\u002Fnecko\u002Fxpcshell` inside the package. See [docs\u002Fmacos.md](docs\u002Fmacos.md). The PyPI wheel is the Windows runtime.\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>\u003Cb>Context hangs or doesn't respond\u003C\u002Fb>\u003C\u002Fsummary>\n\n- Make sure you use the `with` statement (`with Context() as ctx:`) so the process is cleaned up properly.\n- If using a proxy, verify it is reachable: `context = Context(proxy=\"http:\u002F\u002F127.0.0.1:7890\")`.\n- Increase the timeout: `Context(timeout=60)`.\n- On Wine, upgrade to ≥ 0.1.5 which fixes a known navigate deadlock.\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>\u003Cb>JSError: script error\u003C\u002Fb>\u003C\u002Fsummary>\n\nThe site's JavaScript threw an exception. Inspect `error.name`, `error.message`, and `error.stack` to locate the cause. If the page relies on APIs not yet implemented, the stack trace will point to the missing call.\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>\u003Cb>How do I use a proxy?\u003C\u002Fb>\u003C\u002Fsummary>\n\n```python\nwith Context(proxy=\"http:\u002F\u002F127.0.0.1:7890\") as ctx:\n    ctx.navigate(\"https:\u002F\u002Fexample.com\u002F\")\n```\n\nHTTP, HTTPS, and SOCKS5 proxies are supported per-context.\n\u003C\u002Fdetails>\n\n\u003Cdetails>\n\u003Csummary>\u003Cb>How do I run examples?\u003C\u002Fb>\u003C\u002Fsummary>\n\n```sh\npython -m ruyijs.examples              # list all examples\npython -m ruyijs.examples akamai --proxy http:\u002F\u002F127.0.0.1:7890\npython -m ruyijs.examples creepjs\n```\n\u003C\u002Fdetails>\n\n## 💬 Contact\n\nQuestions, permission requests, commercial licensing, or just to talk shop — add the author on WeChat, or scan to join the discussion group.\n\n\u003Ctable>\n\u003Ctr>\n\u003Ctd align=\"center\">\u003Cimg src=\"assets\u002Fwechat-qr.jpg\" alt=\"WeChat QR — 如意\" width=\"220\">\u003Cbr>\u003Cb>WeChat:\u003C\u002Fb> 如意\u003C\u002Ftd>\n\u003Ctd align=\"center\">\u003Cimg src=\"assets\u002Fwechat-group-qr.jpg\" alt=\"Group — AI浏览器ruyijs过检测交流群3\" width=\"220\">\u003Cbr>\u003Cb>Group:\u003C\u002Fb> AI浏览器ruyijs过检测交流群3\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftable>\n\n> The group QR refreshes periodically; if it has expired, add the author on WeChat (如意) for an invite.\n\n## 📄 License\n\nff-ruyijs is **dual-licensed**:\n\n- **[AGPL-3.0](LICENSE)** — free for any use, commercial included, as long as you comply: if you distribute ff-ruyijs or a modified version, **or offer it to users over a network** (a hosted service or API), you must publish the complete source of your version under the AGPL-3.0.\n- **[Commercial license](COMMERCIAL-LICENSE.md)** — required to use ff-ruyijs in closed-source products or services, or to keep your modifications private.\n\nFor a commercial license, contact via WeChat (ID: **如意**) or open an issue on [GitHub](https:\u002F\u002Fgithub.com\u002Fcutetailbud\u002Fff-ruyijs) titled \"Commercial license\".\n\nMozilla-derived code (SpiderMonkey, Necko) stays under the MPL-2.0. Releases up to 0.2.1 keep the license they shipped with.\n\nSee [`NOTICE`](NOTICE) for third-party attributions.\n\nIf ff-ruyijs saved you from reimplementing a browser, a ⭐ helps others find it.\n","2026-10-31 04:26:22","CREATED_QUERY"]