kaizoku-core
v0.3.4
Published
TypeScript scrapers used by [kaizoku.site](https://kaizoku.site): anime, movies/TV, torrents, live TV, plus TMDB / AniList / Kitsu metadata and a handful of video extractors.
Readme
kaizoku-core
TypeScript scrapers used by kaizoku.site: anime, movies/TV, torrents, live TV, plus TMDB / AniList / Kitsu metadata and a handful of video extractors.
The package is isomorphic (fetch only). It does not include a browser, Puppeteer, or a residential proxy. Whether a provider works is mostly a function of where your process egresses from, not of the TypeScript.
Where you should run this
These sites treat well-known datacenter IP ranges as bots. If you deploy the scraper on Vercel, Netlify, AWS Lambda, Cloudflare Pages Functions as the only hop, or similar, expect mass 403s, empty JSON, and Cloudflare challenge HTML. That is normal. It is not a bug in this repo.
Prefer a VPS (Hetzner, OVH, a random small provider, even a $5 box) whose IP is not sitting in a hyperscaler range every WAF already lists. Run Node there, or put this package behind your own API on that machine.
Cloudflare Workers can still work, but Workers share Cloudflare egress. Some origins are fine with that; others 403 anything that looks like CF. For those you add a non-Cloudflare fetch proxy (Cloud Run, Fly, the same VPS) — see Proxies.
Residential or “stealth” VPS egress is what UniqueStream, XAnime, AnimeOnsen, and MKissa usually need. A Worker in iad will not magically pass their checks.
Test from the environment you will actually ship. A provider that works on your laptop can fail on a Worker, and the reverse is also true.
Proxies
configure() talks about three URLs. Two of them fetch pages/APIs while scraping. The third rewrites playback URLs for the video player.
| Option | What it is | When you need it |
|---|---|---|
| scrapeProxyUrl + scrapeProxyKey | Cloudflare Worker at POST {url}/fetch. Used by httpGet / httpPost (most anime HTML/JSON). | You are on a VPS/Node and want a Worker to solve or absorb Cloudflare challenges (AnimeDunya is the usual case). If this package already runs on Workers, try without it first — a second Worker hop is often redundant. We have not proven every provider without it, so A/B it. |
| movieProxyUrl + movieProxyKey | Same /fetch contract, but the host must not be Cloudflare. Used by movieFetch (almost every movie provider, plus AniZone and XAnime). Falls back to scrapeProxyUrl if unset. | You are scraping from Cloudflare Workers (or any CF product) and the origin blocks CF IPs. Point this at Cloud Run, Fly, or a VPS. If you already run on a VPS, leave it unset — movieFetch uses native fetch from that box. |
| proxy.proxyUrl | Media proxy. Rewrites Source.proxiedUrl so the browser can play streams that need Referer/Origin. Unrelated to scraping. | Your player cannot hit the CDN directly. If unset, proxiedUrl stays undefined (it will not silently copy url). |
anilistProxyUrl is the same physical fetch proxy as movieProxyUrl in our deploy (FETCH_PROXY_URL). AniList itself is public; the hop exists so GraphQL from a Worker does not trip AniList’s WAF.
Environment aliases the config object already reads:
SCRAPE_PROXY_URL/SCRAPE_PROXY_KEY→ scrape WorkerFETCH_PROXY_URL/FETCH_PROXY_KEY→ fetch proxy (movieProxyUrland, unless overridden, AniList)PROXY_URL→ playbackproxy.proxyUrlTMDB_API_KEY
AniZone (and similar)
AniZone is not “magic bypass.” Its scrape uses movieFetch. On a VPS that is just fetch. On Workers you must set movieProxyUrl to a non-Cloudflare /fetch service, otherwise Livewire/HTML comes back 403. The hostname is also on the scrape-Worker skip list in src/utils/http.ts, so it will not go through scrapeProxyUrl even if that is set.
If a provider 403s only after you turn on scrapeProxyUrl, check that skip list — some origins reject the Worker’s egress and want your VPS (or the fetch proxy) instead.
Features
- Movie & TV scrapers (TMDB id): VidNest, VidFast, VidZee, VidUp, Purstream, VidCore, VidRock, Icefy, LM Script (movies only), RiveStream, VidGod, CineJoy, ZxcStream. Downloads: TrendiMovies.
- Anime scrapers (AniList / MAL / site ids): see the table below. Several are implemented but currently unusable from datacenter IPs.
- Airing schedule from AniList
AiringSchedule, daily/weekly, timezone offset in minutes. - AniList & Kitsu search/detail/trending. Cross-site ids via ARM (
arm.haglund.dev). - Torrents: Torrentio (TMDB → IMDB) and Nyaa (
anilist.torrents.nyaa/kitsu.torrents.nyaa). - Live TV M3U aggregators.
- TMDB details, search, auth, lists.
- AniSkip intro/outro ranges merged into anime
VideoStreams.
Removed from this package (do not call them): Peachify, VidLink (as a top-level provider), VidRift, VixSrc, Vidvault, Mapple, Cinesu, Opstream, Gojo, AnimePahe. VidNest may still list a “VidLink” server internally.
Provider status
Status is “does the scraper return playable URLs from a normal VPS / our fetch proxy,” not “the TypeScript compiles.” Origins die without notice.
Movie & TV
| Provider | Status | Notes |
|---|---|---|
| VidUp | Active | Enc/dec handshake. Solid default. |
| VidFast | Active | Same family as VidUp. Movies more reliable than some TV rows. |
| VidCore | Active | Encrypted server list via enc-dec.app. |
| VidRock | Active | AES-GCM payloads. Occasional title-specific blanks. |
| VidZee | Active | WASM decrypt. |
| VidNest | Active | Several backup servers (including a VidLink path). Try another server if one 429s. |
| Purstream | Active | Title lookup + catalog stream. Uses httpGet. |
| Icefy | Active | Direct HLS. |
| RiveStream | Active | HLS/MP4 mix. |
| LM Script | Active | Movies only — no TV. |
| VidGod | Implemented | Hit-or-miss; we disable it in the Kaizoku app until it settles. |
| CineJoy | Implemented | WASM. Same: test before you ship it. |
| ZxcStream | Implemented | Can return an empty source list. |
| TrendiMovies | Active | Downloads, not a watch provider (tmdb.fetchDownloads). |
Anime
| Provider | Status | Egress | Notes |
|---|---|---|---|
| AniKoto | Active | VPS or Worker | Large library. Servers vidstream-2, hd-1, vidplay-1. Sub/dub via fetchSources(id, 'sub' \| 'dub'). |
| MegaPlay | Active | VPS or Worker | AniList id. HMAC-signed CDN URLs; tiktokcdn-style bodies are junk-stripped in the media proxy. |
| KickAssAnime | Active | VPS or Worker | Real <show>/<ep> ids. Dub is often an HLS audio track, sometimes a separate dubId. Empty servers on a dub locale ≠ provider down. |
| AniBD | Active | VPS or Worker | AniList id, no title mapping. Sub only. |
| AniZone | Active | VPS, or Worker + fetch proxy | Multi-audio m3u8. Uses movieFetch. |
| 2Dhive | Active | VPS or Worker | MAL id, no title mapping. |
| AniKage | Active | VPS or Worker | AniList id. Collects koto/uwu/kiwi/megg/dib/wave. Prefer url over proxiedUrl in the browser. |
| AnimeSogo | Active | VPS or Worker | AniKoto-like catalog and MegaPlay extractors. |
| AnimeDunya | Active | Scrape Worker helps | MAL id, sub only. Cloudflare-managed challenge — scrapeProxyUrl is the usual fix. |
| AnimeParadise | Active | VPS or Worker | HLS + subs. Sub only. Prefer direct url. |
| AniNeko | Down | — | Site currently dead. Code still exported. |
| AniDB | Maintenance | — | Streams not usable right now. |
| AniWaves | Streams broken | — | EchoVideo + numeric HLS is implemented; live playlists currently fail. |
| MKissa | Needs VPS / challenge | Residential or challenge-capable VPS | Captcha is not solved. Throws NEED_CAPTCHA. |
| AnimeOnsen | Needs VPS | Residential/VPS | Session cookie scrape. Same class of failure as XAnime/UniqueStream from Workers. |
| XAnime | Needs VPS | Residential/VPS | GraphQL {v,iv,ct} via movieFetch. CF egress often 403s. |
| UniqueStream | Needs VPS | Residential/VPS | AES unwrap (x-am-media-id). |
| Senshi | 403 on rewrite | — | Sources come back; playback/proxy rewrite 403s. |
| AnimeNoSub | 403 on rewrite | — | Same. |
| ReAnime | Unplayable | — | Often a single .ts / not a real #EXTM3U; CDN 403s. |
| AnimeGG | Unreliable handoff | — | Sub/dub flags lie; player handoff is messy. |
| AnimeUnity | Italian | — | Italian audio/subs. Skip for an EN catalog. |
| AllManga | List only | — | Search/episodes work; sources AA_CRYPTO_MISSING. |
| Gojo | Removed | — | animetsu.live dead. |
| AnimePahe | Removed | — | JS challenge; needs a real browser. |
Audio: how sub/dub is exposed
Getting this wrong is how you ship a Dub button that does nothing.
| Pattern | Providers | How to switch |
|---|---|---|
| Category argument | AniKoto, MegaPlay, AniNeko, 2Dhive, AnimeSogo, AniKage, AniWaves | fetchSources(id, 'sub' \| 'dub') |
| Audio tracks in the manifest | AniZone, KickAssAnime (most shows) | Do not change the URL. Switch audioTrack in the player. |
| Separate episode id | KickAssAnime | Episode.dubId, or fetchSources(id, 'dub') |
| Sub only | AniBD, AnimeDunya, AnimeParadise, AnimeOnsen | No audio parameter. |
AniBD manifests have no EXT-X-MEDIA:TYPE=AUDIO. KickAssAnime hasDub means “a dub row exists,” not “a dub file plays.”
Installation
npm install kaizoku-core
# or
pnpm add kaizoku-coreRequires a global fetch (Node 18+, Bun, Workers, modern browsers).
Configuration
import { configure } from "kaizoku-core";
configure({
tmdbApiKey: process.env.TMDB_API_KEY,
// Playback rewrite for the player (optional)
proxy: {
enabled: true,
proxyUrl: process.env.PROXY_URL,
generateSourceProxies: true,
},
// Cloudflare Worker scrape hop (optional — see Proxies)
scrapeProxyUrl: process.env.SCRAPE_PROXY_URL,
scrapeProxyKey: process.env.SCRAPE_PROXY_KEY,
// Non-Cloudflare fetch hop (optional — see Proxies)
movieProxyUrl: process.env.FETCH_PROXY_URL,
movieProxyKey: process.env.FETCH_PROXY_KEY,
anilistProxyUrl: process.env.FETCH_PROXY_URL,
anilistProxyKey: process.env.FETCH_PROXY_KEY,
});Both scrape and fetch proxies expect:
POST /fetch
X-Proxy-Key: <key>
Content-Type: application/json
{ "url": "https://…", "method": "GET", "headers": { }, "body": "…" }and return the upstream response.
proxy.proxyUrl
Rewrites each stream to your media proxy with Referer/Origin in the payload. Consumers that prefer url over proxiedUrl (AniKage, AnimeParadise, AniZone in the Kaizoku app) skip this on purpose when the CDN allows browser playback.
Quick start
Movies / TV (TMDB id)
import { tmdb } from "kaizoku-core";
const stream = await tmdb.fetchSourcesByProvider("vidup", "27205", "movie");
console.log(stream.sources[0].url);
const all = await tmdb.fetchAllSources("84958", "tv", 1, 1);Torrents
import { torrentio, nyaa, anilist, kitsu } from "kaizoku-core";
const result = await torrentio.fetchTorrents("550", "movie");
const page = await nyaa.search("Frieren 1080p");
const fromAnilist = await anilist.torrents.nyaa(154587, { episode: 1 });
const fromKitsu = await kitsu.torrents.nyaa(12, { episode: 1 });Live TV
import { livetv } from "kaizoku-core";
const xumo = await livetv.fetchLiveTV("xumo");
const all = await livetv.fetchLiveTV("all");TMDB meta
import { tmdb } from "kaizoku-core";
const movie = await tmdb.getMovieDetails("27205");
const account = await tmdb.getAccountDetails(userAccessToken);AniList / Kitsu / anime scrapers
import { anilist, kitsu, anikoto } from "kaizoku-core";
const trending = await anilist.getTrending(1, 10);
const kitsuTrending = await kitsu.getTrending(1, 10);
const anilistEpisodes = await anilist.fetchEpisodesByProvider("166240", "anikoto");
const stream = await anikoto.fetchSources(anilistEpisodes[0].id, "sub");Kitsu list helpers use the same names as AniList (getPopularThisSeason, searchAnime, …) but Kitsu field names (canonicalTitle, averageRating, …). getTrending is a single curated list — later pages are empty.
Cross-site ids: fetchArmIds("kitsu", 12) → { anilist, myanimelist, … }. Direct-id scrapers (AniBD, 2Dhive, AniKage, …) use that inside kitsu.fetchEpisodesByProvider / anilist.fetchEpisodesByProvider.
Airing schedule
Uses AniList AiringSchedule, not nextAiringEpisode (that field is only the next episode per show).
import { anilist } from "kaizoku-core";
const today = await anilist.getDailySchedule(new Date(), 180); // UTC+3
const week = await anilist.getWeeklySchedule(new Date(), 180);The week is one range, bucketed locally.
Episode ids
Episode.id is the provider’s real id when they have one. Synthetic ids re-run fuzzy mapping on every play and can silently switch shows.
| Provider | Episode.id |
|---|---|
| KickAssAnime | one-piece-0948/ep-1-225ebd |
| AniNeko | frieren-beyond-journeys-end/ep-1 |
| AniBD | 21eop1web |
| 2Dhive, AnimeDunya | <malId>/<episodeNumber> |
No title mapping in the status table means fetchAnimeInfo takes an AniList (or MAL) id and never fuzzy-matches titles. Prefer those when they cover the show.
Build & test
pnpm run build
pnpm run testMost provider tests hit live sites. They are slow and fail when the origin is having a day. AnimeDunya skips without a scrape proxy:
SCRAPE_PROXY_URL=https://your-scrape-worker.example pnpm testCI only runs offline suites:
npx vitest run src/tests/astro.test.ts src/tests/proxy.test.ts src/tests/mapping.test.tsmade by pato ❤️
License
MIT © Kaizoku
