Node.js stream resolver for vidcore.io: an embed scraper, encrypted catalog API client, and HLS proxy with an in-browser player. Pass a TMDB movie or TV id; the server reverse-engineers the site’s handshake, resolves M3U8 URLs per mirror, and plays (or exports) them.
A watch page is not the stream. The playlist never sits in the HTML. The official player scrapes its own embed payload, posts sealed tokens to opaque catalog endpoints, decrypts the response, then hits CDN hosts that reject ordinary browser requests from another origin. This repo implements that chain as a local scraper → resolver → proxy pipeline and a small REST API.
TypeScript, Node.js 20+, ESM. Server via tsx; UI builds into dist/.
- What Gets Recovered
- Architecture
- Scraper
- Resolver
- Proxy and Player
- Playback Hardening
- Stack and Layout
- Run
- HTTP API
- Disclaimer
Reverse engineering the client bundles and live traffic maps to four layers:
| Artifact | Source | Role in this repo |
|---|---|---|
en session token |
Next.js props in embed HTML | Scraper extracts it |
| Server catalog | Encrypted list API response | Resolver decrypts names + unlock tokens |
| Stream config | Encrypted unlock API response | Resolver decrypts the M3U8 url |
| Manifests / segments | CDN (moon, studyedu, /vd/…) |
Proxy rewrites and relays for the player |
Crypto was taken from the site’s own path (not brute-forced):
- List request seal — custom pipeline around AES-CBC of
en(src/resolver/crypto/token.ts) - Catalog / unlock open — AES-256-GCM (
src/resolver/crypto/payload.ts)
flowchart LR
id[TMDB id] --> scrape[Scraper]
scrape -->|en cookies meta| resolve[Resolver]
resolve -->|catalog API unlock| urls[M3U8 URLs]
urls --> proxy[HLS proxy]
proxy --> ui[hls.js player]
urls --> export[VLC MPV]
- Parse movie / TV input for the resolve API.
- Scrape the embed; keep cookies and browser-like headers.
- Call the catalog list API; unlock each mirror in preference order.
- Attach a proxied play URL when the mirror needs the HLS relay.
- Stream NDJSON as each unlock finishes so the player can start on the first success.
Code: src/scraper/
The scraper loads the same embed document the site uses for movies and episodes.
- Path
/movie/{id}or/tv/{id}/{season}/{episode} entoken plus title / year from serialized page props- Cookie jar for later catalog POSTs
- Referer bound to the embed URL and scraper request headers
GETthe HTML (scraperHeadersinrequest.ts).- Persist
Set-Cookie(session.ts). - Parse props (
embed.ts→scrapeEmbedPage). - Return
EmbedSnapshot:{ en, meta, referer, jar }.
Unlocking mirrors is out of scope here — the scraper only rebuilds the session the player would have after the first page load.
Code: src/resolver/
The resolver turns a TMDB id into concrete stream URLs by replaying the encrypted catalog API.
resolvePlayback (pipeline.ts) yields events as it goes:
- Run the scraper.
- Emit
meta. - Build
createScraperFetchfor cookie + referer POSTs. listCatalogServers—encryptResolveToken(en), list action POST,decryptResolvePayload.- Emit
serverlist. - For each preferred server with a
datatoken:unlockCatalogStream, emitserverwithms,url, and proxy fields. - Emit
erroronly if every unlock fails.
Unlock order: Orbit → Supreme → Prime → Premiere 4K → Horizon.
| Step | Function |
|---|---|
| Seal list token | encryptResolveToken |
| Decrypt list / unlock body | decryptResolvePayload |
| Endpoints | Fixed mo base + list / stream action ids in catalog.ts |
| Event | Payload |
|---|---|
meta |
Title, year |
serverlist |
Mirror names |
server |
ok, ms, url, play, referer / proxy flags |
error |
Stage + message |
Progressive resolve keeps the UI responsive: per-mirror timings and early playback without waiting for the full unlock pass.
Code: src/proxy/ · UI: web/player/ (hls.js)
Direct M3U8 links often work in VLC or MPV when a referer can be set. The in-page player cannot rely on that:
- CORS — CDN origins differ from the UI host; hls.js needs readable manifests and segments.
- Forbidden request headers — page scripts cannot set
Refererthe way the CDN expects. - Origin gating — some
/vd/endpoints return403forOrigin: http://localhost:…and succeed when the request is made like the embed site. The proxy sits on the server, sendsReferer: https://vidcore.io/(overridable), and adds CORS for the UI.
The resolver therefore returns:
url— upstream M3U8 for export / external playersplay—/api/hls?url=…&server=…for the built-in player
serveProxyHls (hls.ts):
- Upstream GET with keep-alive, optional
Range, and site referer. - Playlists (
.m3u8) — rewrite media lines andURI="…"through/api/hls. - Segments — pipe bytes; optional MIME from the server registry (e.g. Orbit
video/mp2t, Horizonvideo/mp4).
servers.ts maps mirror name → proxy flags and segment type. Unknown names stay non-proxied.
CDN behavior for Prime / Supreme–style /vd/ streams was reverse-engineered from live unlocks and segment fetches:
- Unlock playlists often land on the moon host.
- Media lines may point at studyedu with the same
/vd/token (identical bytes on both). - Local UI Origin without proxy → 403.
- Racing moon and studyedu on one keep-alive agent could play the first init, then hang: aborting the losing request stalled later GETs on the agent.
Proxy rules now:
- Rewrite
/vd/…playlist targets toward moon when proxied. - Try moon first (socket timeout).
- Fall back to studyedu only after moon fails — sequential, never parallel destroy.
- One upstream path per request so keep-alive stays clean.
- Forward
Rangefor seek.
The player reports time-to-first-frame and can switch mirrors without re-scraping.
| Piece | Detail |
|---|---|
| Runtime | Node.js ≥ 20, ESM, tsx for src/ |
| Language | TypeScript (strict); UI compiled to dist/ |
| HTTP / fetch | node:http, native fetch |
| Crypto | node:crypto |
| Browser HLS | hls.js via /vendor/hls.mjs |
src/
server.ts entry
config.ts PORT, site origin, user-agent
scraper/ embed scrape + session
resolver/ parse, catalog API, pipeline, crypto
proxy/ HLS rewrite + mirror registry
http/ router + static (dist/)
web/ UI source
dist/ built UI (gitignored)
| Env | Default | Use |
|---|---|---|
PORT |
3000 |
Listen port |
HOST |
unset | Bind when set |
VIDCORE_ORIGIN |
https://vidcore.io |
Scraper / referer origin |
USER_AGENT |
Chrome desktop | Upstream UA |
npm install
npm startBuilds web/ → dist/, clears the port, starts tsx src/server.ts. UI: http://localhost:3000/.
Stream resolver endpoint. Scrapes the embed, then unlocks mirrors.
| Query | Required | Description |
|---|---|---|
type |
yes | movie or tv |
id |
yes | TMDB id |
season |
tv | Season |
episode |
tv | Episode |
Content-Type: application/x-ndjson. Bad input → 400 JSON.
HLS proxy for manifests and segments.
| Query | Required | Description |
|---|---|---|
url |
yes | Absolute upstream URL |
server |
yes | Mirror name in the registry |
Rewritten M3U8 or proxied media with CORS.
For education and research into embed scrapers, encrypted catalog APIs, HLS resolvers, and CDN proxies.
Does not host or redistribute media. Upstream sites remain separate services. Comply with copyright, terms of service, and local law. No warranty.