Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Anime Nexus HLS Stream Resolver

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.

Table of contents

Overview

Inputhttps://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)

Entry points

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.

Resolve flow

lib/core/resolve.mjs — shared openStream() for both resolve() and resolvePlayback():

  1. parseWatchUrl(input){ episodeId, watchUrl }
  2. randomUUID() → client fingerprint
  3. getCfSession({ episodeId })curl-cffi-node session with cf_clearance
  4. In parallel:
    • fetchStreamMeta(episodeId, fingerprint, client) → API JSON
    • fetchAttestation(watchUrl, client){ ref, secret } from watch HTML
  5. pickStream(meta){ url, videoId, subtitles, videoMeta } from data.hls
  6. VideoSocket.connect() with episode id, fingerprint, HLS URL, attestation
  7. manifestToken(manifestPath(stream.url)) → first CDN token
  8. signUrl(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.

Cloudflare clearance

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:

  1. Read __CF$cv$params from HTML (lib/cf/challenge.mjs)
  2. Fetch /cdn-cgi/challenge-platform/scripts/jsd/main.js, parse with lib/cf/jsdctl.mjs
  3. POST compressed browser payload (lib/cf/browser.mjs + lib/cf/lz.mjs) to the JSD oneshot URL
  4. Confirm cf_clearance in exported cookies

Cached 20 minutes (CF_TTL in session.mjs), then re-cleared.

Stream metadata and attestation

Metadatalib/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.

Attestationlib/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).

VideoSocket

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.

CDN signing

lib/nexus/stream.mjssignUrl(base, token, sessionId, requestType, segmentPath?):

{base}?token=…&requestType=…&sessionId=…[&segmentPath=/…]

lib/hls/playlist.mjstokenizeCdnUrl(socket, videoId, absoluteUrl):

  • Segment path parsed by parseSegment() → segment token + segmentPath param
  • Init segments (_init-{n}.mp4) and playlists → manifest token

lib/cdn/gateway.mjscdnHeaders(session) merges socket headers with Origin: SITE_ORIGIN and Referer: watchUrl.

Playback gateway

lib/core/proxy.mjs — in-memory sessions map keyed by playId (randomUUID()), TTL 30 minutes.

openSession(watchUrl)

  1. resolvePlayback(watchUrl)
  2. Sign and fetch master; store masterBody
  3. 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.

Browser UI

public/index.html + ui.js:

  1. Submit watch URL → GET /api/resolve?url=…
  2. Display watchUrl and playUrl in export fields (copy buttons)
  3. Load playUrl into hls.js 1.6.16 (dynamic import from jsDelivr)
  4. 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.

Stack

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

Project layout

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

Usage

npm install
npm start

Open 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.

HTTP routes

GET /api/resolve?url={watchUrl}

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.

GET /play/{playId}/master.m3u8

Rewritten master playlist. Content-Type: application/vnd.apple.mpegurl. CORS *.

GET /play/{playId}/x/{host}/{path…}

Proxied CDN resource. M3U8 responses rewritten; other content streamed with upstream Content-Type. Query string forwarded.

Static

Path File
/, /index.html public/index.html
/ui.css public/ui.css
/ui.js public/ui.js

CLI stdout

All fields from resolve() in lib/core/resolve.mjs: episodeId, watchUrl, videoId, fingerprint, sessionId, masterUrl, signedMasterUrl, subtitles, videoMeta, headers.

Disclaimer

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.

About

Resolve anime.nexus watch URLs to signed HLS streams with CDN auth headers and session tokens; CLI for export, local server with streaming CDN gateway and web player.

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Contributors

Languages