Self-hosted Node.js tool that takes an anime.nexus watch URL, runs the same clearance + socket + CDN signing steps as the site player, and exposes the result through a CLI JSON dump, a local HLS gateway, and a small web UI.
- Overview
- Entry points
- Resolve flow
- Cloudflare clearance
- Stream metadata and attestation
- VideoSocket
- CDN signing
- Playback gateway
- Browser UI
- Stack
- Project layout
- Usage
- HTTP routes
- Disclaimer
Input — https://anime.nexus/watch/{episodeId}[/{slug}], parsed by lib/nexus/watch.mjs.
Output depends on which entry point you use:
| Entry | Function | Socket after resolve | Primary output |
|---|---|---|---|
CLI (cli.mjs) |
resolve() |
Closed | JSON: signedMasterUrl, headers, subtitles, videoMeta |
| Server resolve | openSession() |
Open (30 min) | JSON: playUrl pointing at local gateway |
| Gateway routes | serveMaster / servePlaylist / pipeCdn |
Open | Rewritten M3U8 + streamed .m4s through /play/{playId}/… |
The unsigned HLS master lives in the stream API response (data.hls). Every CDN fetch still needs a fresh token from VideoSocket.getToken() plus the header set from cdnHeaders().
Origins (lib/nexus/site.mjs):
| Constant | Host |
|---|---|
SITE_ORIGIN |
https://anime.nexus |
API_ORIGIN |
https://api.anime.nexus |
SOCKET_WS |
wss://prd-socket.anime.nexus |
| CDN | Hostname embedded in data.hls (e.g. video.anime.delivery) |
cli.mjs → resolve(watchUrl) → stdout JSON, exit
server.mjs → GET /api/resolve → { playUrl, signedMasterUrl, … }
→ GET /play/{id}/master.m3u8 → proxied HLS
→ GET /play/{id}/x/{host}/… → signed CDN fetch
public/ui.js → fetch /api/resolve → hls.js on playUrl
server.mjs builds playUrl and all rewritten playlist lines from the request Host header (requestOrigin) — no fixed bind address in source.
lib/core/resolve.mjs — shared openStream() for both resolve() and resolvePlayback():
parseWatchUrl(input)→{ episodeId, watchUrl }randomUUID()→ client fingerprintgetCfSession({ episodeId })→curl-cffi-nodesession withcf_clearance- In parallel:
fetchStreamMeta(episodeId, fingerprint, client)→ API JSONfetchAttestation(watchUrl, client)→{ ref, secret }from watch HTML
pickStream(meta)→{ url, videoId, subtitles, videoMeta }fromdata.hlsVideoSocket.connect()with episode id, fingerprint, HLS URL, attestationmanifestToken(manifestPath(stream.url))→ first CDN tokensignUrl(stream.url, token, sessionId, 'manifest')→ signed master
resolve() returns export fields and closes the socket in finally.
resolvePlayback() returns { socket, http, episodeId, videoId, watchUrl, masterUrl } for the gateway.
lib/net/session.mjs creates one shared API session via httpClient() (lib/net/fetch.mjs, Chrome 131 impersonation) and calls solveCloudflare(streamApiUrl(episodeId), client).
lib/cf/solve.mjs when challenged:
- Read
__CF$cv$paramsfrom HTML (lib/cf/challenge.mjs) - Fetch
/cdn-cgi/challenge-platform/scripts/jsd/main.js, parse withlib/cf/jsdctl.mjs - POST compressed browser payload (
lib/cf/browser.mjs+lib/cf/lz.mjs) to the JSD oneshot URL - Confirm
cf_clearancein exported cookies
Cached 20 minutes (CF_TTL in session.mjs), then re-cleared.
Metadata — lib/nexus/stream.mjs
GET {API_ORIGIN}/api/anime/details/episode/stream?id={episodeId}&fillers=true&recaps=true
Headers include X-Client-Fingerprint and X-Fingerprint. Response data.hls is the master URL; videoId is parsed from /video/{uuid}/stream/ in that URL.
Attestation — lib/nexus/attest.mjs
GET {watchUrl}
Regex scrape from HTML:
attestRef:"…"wireSecret:"…"
Fed into socket auth as HMAC-SHA256 proof (wireProof in lib/net/socket.mjs).
lib/net/socket.mjs — Socket.IO v4, namespace /video.
Connect URL:
wss://prd-socket.anime.nexus/api/socket/?videoId={episodeId}&fingerprint={uuid}&m3u8Url={encoded-hls}&EIO=4&transport=websocket
Handshake: Engine.IO ping/pong → 40/video, → auth packet → connected with sessionId.
getToken payloads:
requestType |
Fields | Signed resource |
|---|---|---|
manifest |
manifestUrl, videoId, prevToken |
Master, media playlists, init .mp4 |
segment |
variant, segIdx, track, videoId |
.m4s matching .{variant}_{segIdx}_{track}.m4s |
Inflight dedup via tokenInflight map. manifestToken chains prevToken through lastManifestToken.
Post-auth challenge (when present) adds X-Challenge and X-Encrypted-Secret to CDN headers — forwarded as-is, not decrypted locally.
lib/nexus/stream.mjs — signUrl(base, token, sessionId, requestType, segmentPath?):
{base}?token=…&requestType=…&sessionId=…[&segmentPath=/…]
lib/hls/playlist.mjs — tokenizeCdnUrl(socket, videoId, absoluteUrl):
- Segment path parsed by
parseSegment()→ segment token +segmentPathparam - Init segments (
_init-{n}.mp4) and playlists → manifest token
lib/cdn/gateway.mjs — cdnHeaders(session) merges socket headers with Origin: SITE_ORIGIN and Referer: watchUrl.
lib/core/proxy.mjs — in-memory sessions map keyed by playId (randomUUID()), TTL 30 minutes.
openSession(watchUrl)
resolvePlayback(watchUrl)- Sign and fetch master; store
masterBody - Return
{ playId, watchUrl, signedMasterUrl, videoId, sessionId, fingerprint, headers }
Serving
| Route | Module call | Notes |
|---|---|---|
/play/{playId}/master.m3u8 |
serveMaster |
rewritePlaylist on cached master |
/play/{playId}/x/{host}{path} (.m3u8) |
servePlaylist |
Upstream fetch, rewrite, warmInit for #EXT-X-MAP |
/play/{playId}/x/{host}{path} (binary) |
pipeCdn |
streamCdn pipes response body to client |
Rewrite pattern (lib/hls/playlist.mjs):
{request-origin}/play/{playId}/x/{cdn-host}{cdn-path}
Playlist bodies cached per upstream URL in session.playlistBodies. Init URLs tracked in session.inits to avoid duplicate warms.
Expired or unknown playId → error, socket closed.
public/index.html + ui.js:
- Submit watch URL →
GET /api/resolve?url=… - Display
watchUrlandplayUrlin export fields (copy buttons) - Load
playUrlinto hls.js 1.6.16 (dynamic import from jsDelivr) - Resolve and playback timers via
performance.now()
Playback uses the gateway URL so tokens and headers are applied server-side on each segment request. hls.js required on Chrome/Firefox; Safari can play HLS natively if pointed at playUrl.
| Piece | Implementation |
|---|---|
| HTTP + WebSocket client | curl-cffi-node (impersonate: chrome131) |
| Cloudflare JSD | lib/cf/* |
| Attestation HMAC | node:crypto subtle.sign |
| HTTP server | node:http |
| Modules | ES modules (.mjs) |
| UI playback | hls.js 1.6.16 |
| Env | Default | Used in |
|---|---|---|
PORT |
3000 |
server.mjs listen |
cli.mjs
server.mjs
lib/core/resolve.mjs resolve(), resolvePlayback()
lib/core/proxy.mjs openSession(), serveMaster(), servePlaylist(), pipeCdn()
lib/nexus/site.mjs origins, streamApiUrl(), watchUrl()
lib/nexus/watch.mjs parseWatchUrl()
lib/nexus/stream.mjs fetchStreamMeta(), pickStream(), signUrl()
lib/nexus/attest.mjs fetchAttestation(), manifestPath()
lib/net/session.mjs getCfSession(), cookiesFor()
lib/net/fetch.mjs httpClient(), fetchBytes(), fetchJson()
lib/net/socket.mjs VideoSocket
lib/net/userAgent.mjs
lib/cf/solve.mjs solveCloudflare()
lib/cf/challenge.mjs extractChallengeParams()
lib/cf/browser.mjs JSD payload builder
lib/cf/jsdctl.mjs challenge script parser
lib/cf/lz.mjs LZ compress for JSD POST
lib/hls/playlist.mjs rewritePlaylist(), tokenizeCdnUrl()
lib/cdn/gateway.mjs cdnHeaders(), warmInit(), streamCdn()
public/index.html
public/ui.css
public/ui.js
npm install
npm startOpen the logged URL (default http://localhost:3000), paste a watch link, click Resolve.
CLI
node cli.mjs "https://anime.nexus/watch/{episodeId}/episode-12"
anime-nexus-hls-resolve "https://anime.nexus/watch/…"One-shot export for scripts. For playback, run the server and use playUrl — the gateway needs the open socket to mint segment tokens.
Opens a gateway session and returns:
{
"watchUrl": "https://anime.nexus/watch/…",
"playUrl": "http://localhost:3000/play/{playId}/master.m3u8",
"signedMasterUrl": "https://…/master.m3u8?token=…",
"videoId": "…",
"sessionId": "…",
"fingerprint": "…",
"headers": { }
}400 if url missing. 500 { "error": "…" } on failure.
Rewritten master playlist. Content-Type: application/vnd.apple.mpegurl. CORS *.
Proxied CDN resource. M3U8 responses rewritten; other content streamed with upstream Content-Type. Query string forwarded.
| Path | File |
|---|---|
/, /index.html |
public/index.html |
/ui.css |
public/ui.css |
/ui.js |
public/ui.js |
All fields from resolve() in lib/core/resolve.mjs: episodeId, watchUrl, videoId, fingerprint, sessionId, masterUrl, signedMasterUrl, subtitles, videoMeta, headers.
This project does not host, store, or distribute media. anime.nexus and its CDN are independent services. The resolver reads public watch pages and calls their APIs the same way a browser would.
You are responsible for complying with copyright law, site terms of service, and local regulations. No warranty. Use only on content you have the right to access.