A standalone replacement frontend for themoviebox.xyz that keeps every category and plays everything through the moviethon overlay player.
No userscript, no browser extension, no iframes of the original site — it is its own app that talks to the same public MovieBox BFF API.
public/ static SPA (vanilla JS, no build step)
index.html
styles.css app theme + the moviethon player styles
api.js API client (token handling, media-proxy URL builder)
app.js hash router + views (home, categories, search, detail) + auth
player.js moviethon player, adapted to a module
vendor/ hls.js + dash.js
server/
index.mjs zero-dependency Node server: static + API proxy + media proxy
bridge.mjs optional Chrome bridge for signed-in sessions
shared/core.js proxy core (request shaping, HLS/DASH rewriting)
Dockerfile container image
research/ the original moviethon userscript (reference)
npm start # http://127.0.0.1:8787 (auto-bumps the port if busy)
PORT=9000 npm startNode 20+ required. There are no dependencies to install.
Or as a container:
docker build -t notmoviebox .
docker run -p 8787:8787 notmovieboxscripts/share.mjs binds the server to all interfaces and prints the URLs other
devices can use — LAN and Tailscale:
npm run share # LAN + Tailscale
npm run share -- --port 9000
npm run share -- --no-tailscale # LAN only
npm run share -- --funnel # also expose publicly via Tailscale Funnel
./scripts/share.mjs --helpOutput looks like:
notmoviebox sharing
local http://127.0.0.1:8787/
LAN http://192.168.1.20:8787/ (en0)
tailnet http://100.99.82.53:8787/ (Tailscale IP — works on your tailnet)
Ctrl+C to stop.
Because the server listens on 0.0.0.0, the Tailscale IP works with no extra
setup — any device on your tailnet can open it. The script additionally tries
tailscale serve to add HTTPS on your *.ts.net name; if the CLI can't (the macOS
GUI build commonly refuses), it says so and you just use the IP URL. Ctrl+C stops
the server, and resets tailscale serve if it was configured.
Flags: --port <n>, --https-port <n>, --funnel, --no-tailscale,
--keep-serve.
Link the CLI onto your PATH so it runs from anywhere:
npm run link # or: npm link (needs a writable global npm prefix)
npm run unlinknpm run link tries npm link first. If the global npm prefix isn't writable
(common with Homebrew/system Node — /usr/local/lib/node_modules is root-owned),
it falls back to symlinking the bin into the first writable directory already on
your PATH, which needs no sudo. It installs two commands:
notmoviebox # share on LAN + Tailscale (default)
nmb # short alias, same thing
notmoviebox serve # local only (127.0.0.1)
notmoviebox share --port 9000
notmoviebox serve --port 8080
notmoviebox help
notmoviebox versionThe MovieBox API and its CDN need two things a plain static page cannot do from a different origin:
- Auth.
/subject/playand/subject/searchrequire a bearer token. The API hands out an anonymous token in thex-userresponse header; the server keeps one warm and injects it on every/api/*call. - Media. The CDN sends no
Access-Control-Allow-Origin, HLS/DASH are fetched withfetch/XHR (so CORS applies), and streams require aReferer, a signed header (signHeaderKey/signCookie) and cookies set by a pre-play API. The browser cannot setRefererat all./media?u=…fetches server-side with the right headers and rewrites HLS playlists so every variant/segment/key keeps flowing through the proxy.
So server/index.mjs is the "no compromises" part: it makes playback work exactly
like it does inside the real site's session.
No account needed, and it plays at 1080p. The API hands anonymous clients the same thing it hands the official player:
- a free MP4 rendition, and
- a DASH ladder at 1080 / 720 / 480 (HEVC video + AAC audio).
Two things gate it, both of which the site itself satisfies:
- Referer. The play request must carry
Referer: https://themoviebox.xyz/movies/<detailPath>?id=<subjectId>&type=/movie/detail&detailSe=&detailEp=&lang=en. Change it to/detail/…(or drop it) and the response comes back with zero streams — same token, same URL, only the Referer differs. - Signed header. The DASH entry ships with
signHeaderKey: "X-MB-Token"and asignCookievalue. Without that header the CDN returns403 ACCESS DENIED; with it you get the full 1080p ladder. This is exactly what moviethon does.
The server mirrors both, and the player defaults to the highest-resolution source available (DASH 1080p), forcing the top rendition and falling back automatically if a codec can't decode. This is "play the best video the API provides", with no popups and no ads.
playConfig.maxResolution: 480 and vipLocked: true flags are still present in the
response, but the API also returns the DASH URL and its signed header to anonymous
clients, so the ladder is reachable. Nothing is forged — the app only uses the URLs
and tokens the API itself returns.
Signing in is entirely optional; it does not change what the free DASH ladder gives you. If you want it anyway, four methods work:
- Browser session — the server launches a real Chrome with a persistent profile
and opens
themoviebox.xyz; session-bound calls run inside that page.~/.notmoviebox/chrome-profile, auto-discovered Chrome, override withNMB_CHROME=/path/to/chrome. - Continue with Google — Firebase popup →
POST /user/google-login. - QR code — scan with the MovieBox mobile app (origin-independent).
- Paste a session token — bookmarklet to copy it from
themoviebox.xyz.
Options 2–4 store the token in localStorage and send it as x-mb-token; the server
forwards it as Authorization: Bearer ….
/media?u=<b64 url>&h=<header name>&v=<header value> fetches the CDN resource
server-side with the right Referer/headers/cookies and adds CORS. HLS playlists are
rewritten so every variant/segment/key URI keeps flowing through the proxy. DASH
manifests get a <BaseURL> pointing at the path-based form
(/media/<token>/<relative>) so dash.js resolves SegmentTemplate URLs back through
us while keeping the signed header.
All content surfaces of the original site:
| Section | Source |
|---|---|
| Home | /home?host=themoviebox.xyz (hero banners + rows) |
| Movies | /subject/filter channelId=1 + genre / year / language filters |
| TV Shows | /subject/filter channelId=2 |
| Anime | /subject/filter channelId=4 |
| Midnight | /tab-operating?tabId=ONEROOM_MIDNIGHT + /subject/trending |
| Top 100 | /ranking-list/content (7 curated lists) |
| Search | /subject/search + /subject/everyone-search suggestions |
Detail pages (/detail) show seasons/episodes, related titles
(/subject/detail-rec) and a trailer when available.
Every home/tab row that is truncated also has a View all link. Rows carry a
genreTopId, and /ranking-list/content?id=<genreTopId> expands it to the full,
paginated list — the same endpoint the site's own "more" button uses. (The one row
without a genreTopId, "Coming Soon", already ships its complete list.)
public/player.js is the moviethon overlay player adapted into a module:
- custom controls — play/pause, seek, volume, speed, PiP, fullscreen
- quality selector (MP4 / HLS / DASH), auto-picks the highest resolution
- season tabs + episode grid with per-episode switching
- single and dual subtitles (SRT/ASS → VTT conversion, language preference remembered)
- resume position per title in
localStorage - keyboard:
space/k,←/→,↑/↓,m,f,c,Esc
Every stream URL is routed through /media, so signed headers, Referer and CDN
cookies are applied server-side rather than relying on the browser.
Tuned for low-end phones:
- Thumbnails are downscaled by the CDN. The originals are enormous — the home
page references ~205 MB of artwork, with single covers up to 4.7 MB. Every
image is requested through
?x-oss-process=image/resize,w_320/format,jpg/quality,q_70, which brings the home page to ~7 MB and a card thumbnail from ~2 MB to ~18 KB.format,jpgmatters: without it OSS re-encodes to PNG and the thumbnail stays ~200 KB. - hls.js + dash.js are lazy-loaded. ~1.2 MB of JS combined, now fetched only when you press play instead of blocking every page load.
- Off-screen content is skipped via
content-visibility: autoon rows and cards, so the 17-row home page and long grids don't lay out/paint offscreen. backdrop-filteris disabled on mobile — a common cause of scroll jank on weak GPUs.- Images use
loading="lazy"anddecoding="async".
GET /home GET /detail
GET /tab/get-bottom-tab-list GET /subject/detail-rec
GET /tab-operating GET /ranking-list/content
GET /subject/trending GET /subject/play
POST /subject/filter GET /subject/caption
POST /subject/search GET /subject/everyone-search
POST /user/google-login POST /user/qr-login-create
POST /user/qr-login-poll POST /user/qr-login-fetch
GET /user/profile POST /user/logout
Base: https://h5-api.aoneroom.com/wefeed-h5api-bff
Unofficial and unaffiliated. It reads the same public API the site itself calls and does not bypass authentication or payment — it requests exactly the free stream the site serves to anonymous visitors, and VIP quality requires a real VIP account. Use it for personal, educational purposes.