Author SHA1 Message Date
cloudflare-workers-and-pages[bot] 8d0e0497f7 Add Cloudflare Workers configuration 2026-07-02 04:05:05 +00:00
17 changed files with 2302 additions and 2137 deletions
-5
View File
@@ -27,8 +27,3 @@ SCRAPER_API_KEY=
# Deploy on Render.com free tier, Oracle Cloud, or any VPS
# Solves Cloudflare Turnstile challenges automatically
FLARESOLVERR_URL=
# Method 4: Scrape.do (1,000 free credits/month)
# Sign up at https://scrape.do/ (no credit card required)
# Handles Cloudflare, proxy rotation, CAPTCHA solving
SCRAPE_DO_TOKEN=
+6
View File
@@ -2,3 +2,9 @@ node_modules/
.env
.vercel/
deploy.cjs
# wrangler files
.wrangler
.dev.vars*
!.dev.vars.example
!.env.example
-110
View File
@@ -1,115 +1,5 @@
# Changelog
## v2.3.3
### Issue #3 Fix — API Stability, Direct Embed Providers & Error Handling
#### New: Direct Embed Providers (bypasses Cloudflare entirely)
- **`megavid.buzz`** — Direct video embed, no scraping needed
- **`anixo.buzz`** — Direct video embed, CORS-friendly
- Added `GET /api/embed/:anilistId/:episode` — Returns embed URLs for all providers
- Added `GET /api/embed/:anilistId/:episode/:provider` — Returns embed URL for specific provider
- Added `GET /api/embed-providers` — Lists all available embed providers
- These endpoints return URLs that can be embedded directly in an iframe — **no Cloudflare blocking**
- Supports `lang=sub|dub` and optional `malId` query params
#### Fixed: Streaming Endpoint Error Handling
- **Cloudflare detection** — Streaming endpoints (`/episodes`, `/stream`, `/watch`, `/download`, `/sources`) now return proper `503` status codes with descriptive error messages when Cloudflare blocks requests
- **Timeout handling** — Direct pipe requests now have 15s timeout with exponential backoff retry (was 20s with less informative errors)
- **Better error messages** — All streaming failures now explain that SCRAPER_API_KEY or FLARESOLVERR_URL can be configured as fallback
#### New: `/api/sources/:id` Endpoint
- Added path-based streaming sources endpoint: `GET /api/sources/:id?provider=kiwi&anilistId=20&category=sub`
- Complements the existing query-based `/api/sources` endpoint
- Returns streaming sources with subtitles, streams, and download info
#### Improved: CORS Configuration
- Added `Authorization` header to CORS allowed headers
- Added `X-RateLimit-Limit` and `X-RateLimit-Remaining` to exposed headers
- Default behavior allows all origins for public API usage by anime frontends
#### Improved: Pipe Health Check
- `/api/pipe-health` now reports skipped (unconfigured) methods separately
- Better recommendation messages when no methods are available
- Includes setup instructions in error responses
#### Changed: Version Bump
- All version references updated from `2.3.2` to `2.3.3`
- Endpoint count updated from 46 to 49 (3 new embed routes)
#### Files Changed
- `src/helpers/pipe.js` — Added EMBED_PROVIDERS config, getEmbedUrl(), getEmbedUrls()
- `src/routes/apiRoutes.js` — Added /embed, /embed-providers routes, improved error handling, updated OpenAPI spec
- `src/helpers/pipe.js` — Better timeout handling, Cloudflare detection, informative error messages
- `server.js` — Updated CORS headers, version bump
- `package.json` — Version bump to 2.3.3
- `test.js` — Added test for `/sources/:id`, version bump
- `CHANGELOG.md` — This entry
#### Issue #3 Checklist
- [x] Fix `/api/search` — Working (AniList GraphQL)
- [x] Fix `/api/info/:id` — Working (AniList GraphQL)
- [x] Fix `/api/trending` — Working (AniList GraphQL)
- [x] Fix `/api/filter` — Working (AniList GraphQL)
- [x] Fix `/api/episodes/:id` — Improved error handling, proper 503 on Cloudflare blocks
- [x] Fix `/api/sources` — Added `/api/sources/:id` path-based endpoint
- [x] Fix `/api/watch` — Improved error handling with Cloudflare detection
- [x] **NEW: Direct embed providers** — megavid.buzz + anixo.buzz bypass Cloudflare entirely
- [x] Consistent JSON responses — All endpoints return `{ success, results/message }`
- [x] Proper 400/404 responses — Input validation for invalid IDs and missing params
- [x] Error handling — Timeouts, Cloudflare blocks, upstream failures handled gracefully
- [x] CORS properly configured — Allows all origins by default for frontend use
#### Notes
- AniList-based endpoints (search, info, trending, filter, etc.) are fully functional
- **Direct embed providers (`/api/embed`) work from anywhere** — no Cloudflare blocking
- Pipe-based endpoints may still be blocked from datacenters — use `/api/embed` instead
- For Cloudflare-protected pipe, configure SCRAPER_API_KEY/FLARESOLVERR_URL as fallback
---
## v2.3.2
### Cloudflare Worker Implementation — Issue #2 Fix
#### New: `worker.js` (Cloudflare Worker Edge Runtime)
- **Full Cloudflare Worker implementation** created at project root
- Edge-to-edge requests *may* bypass Cloudflare bot detection (theoretical — untested)
- Zero cold starts, 100K free requests/day on Cloudflare free tier
- No npm dependencies — uses native `fetch`, `DecompressionStream`, `TextEncoder`/`TextDecoder`
- 15 API endpoints: search, suggestions, filter, trending, trending/daily, trending/weekly, popular, top, upcoming, recent, random, info, episodes, watch, stream, genres, tags, providers, health
#### New: `wrangler.toml`
- Cloudflare Workers deployment configuration
- Compatible with `wrangler deploy` CLI
- Node.js compatibility flag enabled for buffer/text encoding support
- CPU time limit: 30 seconds
#### Architecture (Theoretical)
```
Browser → Cloudflare Worker (edge) → miruro.to → Cloudflare trusts itself ✅ (if edge-to-edge is trusted)
Vercel (datacenter) → miruro.to → Cloudflare blocks ❌
```
#### Deploy
```bash
npm install -g wrangler
wrangler login
wrangler deploy worker.js --name miruroapi
```
#### Files Changed
- `worker.js` — Cloudflare Worker implementation (complete rewrite for edge runtime)
- `wrangler.toml` — Cloudflare Workers deployment configuration
- `CHANGELOG.md` — This entry
#### Notes
- This addresses Issue #2: Cloudflare Worker/Pages deployment support
- Streaming endpoints (`/episodes`, `/watch`, `/stream`) are implemented but **untested** — edge-to-edge bypass is theoretical
- Environment variables `SCRAPER_API_KEY` and `FLARESOLVERR_URL` are optional for fallback methods
- **Not verified in production** — deploy and test at your own risk
- The worker runs on Cloudflare's edge network with zero cold starts
---
## v2.3.1
### Cloudflare Worker Edition — Streaming Bypasses Cloudflare
+1 -61
View File
@@ -30,7 +30,7 @@ npm run dev
MiruroAPI/
├── server.js # Express entry point
├── src/
│ ├── helpers/ # AniList, pipe + embed providers, cache integrations
│ ├── helpers/ # AniList, pipe, cache integrations
│ └── routes/ # Express routes
├── public/ # Static files (landing page, Swagger UI)
└── docs/ # API documentation
@@ -43,9 +43,6 @@ MiruroAPI/
3. Update `docs/endpoints.md` with documentation
4. Test with `curl http://localhost:3000/api/your-endpoint`
> For streaming endpoints, consider adding direct embed providers (megavid, anixo)
> in `src/helpers/pipe.js` — these bypass Cloudflare and work from any environment.
## Code Style
- Use CommonJS (`require`/`module.exports`)
@@ -53,37 +50,6 @@ MiruroAPI/
- Follow existing patterns in the codebase
- Keep helpers focused on one task
### File Headers
```js
/*
* ======= • ======= • ======= • ======= • =======• =======
* MiruroAPI — filename.js
* Repository: https://github.com/Shineii86/MiruroAPI
*
* @description
* What this module does.
*
* @author Shinei Nouzen
* @license MIT
* ======= • ======= • ======= • ======= • =======• =======
*/
```
### Section Markers
```js
// ══════════════════════════════════════════════════════════════
// SECTION NAME
// ══════════════════════════════════════════════════════════════
```
### Feature Markers
```js
// ---- FEATURE: Name ----
```
## Commit Messages
Use [Conventional Commits](https://www.conventionalcommits.org/):
@@ -109,32 +75,6 @@ Use [Conventional Commits](https://www.conventionalcommits.org/):
- Include expected vs actual behavior
- Include your Node.js version
## Contributing Bypass Solutions (Priority)
Streaming endpoints are blocked on cloud platforms. This is the most impactful contribution you can make.
### What to Build
Add a new fallback method in `src/helpers/pipe.js`. The file already has 3 methods (direct, ScraperAPI, FlareSolverr) — add yours as a 4th.
### Free/Open Source Options
| Tool | Language | Install |
|------|----------|---------|
| [nodriver](https://github.com/ultrafunkamsterdam/nodriver) | Python | `pip install nodriver` |
| [Camoufox](https://github.com/nicandris/camoufox) | Python | `pip install camoufox` |
| [curl_cffi](https://github.com/nicandris/curl_cffi) | Python | `pip install curl_cffi` |
| [patchright](https://github.com/nicandris/patchright) | Python | `pip install patchright` |
| [Scrape.do](https://scrape.do) | HTTP API | 1K free credits/mo |
### Steps
1. Fork and clone
2. Add your method in `src/helpers/pipe.js`
3. Add your API key/URL to `.env.example`
4. Test: `curl http://localhost:3000/api/episodes/20`
5. Submit PR
## License
By contributing, you agree that your contributions will be licensed under the MIT License.
+73 -136
View File
@@ -1,6 +1,3 @@
> [!NOTE]
> **API MAYBE Temporarily Paused** due to suspiciously too many requests. My hosted version of this API is only for testing purposes. You MUST host your own instance to use the API.
<div align="center">
<img src="https://capsule-render.vercel.app/api?type=waving&height=300&color=gradient&text=𝙈𝙞𝙧𝙪𝙧𝙤𝘼𝙋𝙄&fontAlignY=30&fontSize=100&desc=𝖠%20𝖢𝗈𝗆𝗉𝗅𝖾𝗍𝖾%20𝖱𝖤𝖲𝖳𝖿𝗎𝗅%20𝖠𝖯𝖨%20𝖥𝗈𝗋%20𝖠𝗇𝗂𝗆𝖾%20𝖲𝗍𝗋𝖾𝖺𝗆𝗂𝗇𝗀&descSize=25" />
@@ -22,15 +19,15 @@
<img src="https://img.shields.io/badge/AniList-GraphQL-02A6E4?style=flat-square&logo=graphql&logoColor=white" alt="AniList GraphQL"/>
<img src="https://img.shields.io/badge/Vercel-Serverless-000000?style=flat-square&logo=vercel&logoColor=white" alt="Vercel"/>
<img src="https://img.shields.io/badge/License-MIT-22c55e?style=flat-square&logo=mit&logoColor=white" alt="License"/>
<img src="https://img.shields.io/badge/Version-2.3.3-f43f8e?style=flat-square&logoColor=white" alt="Version"/>
<img src="https://img.shields.io/badge/Endpoints-49-6366f1?style=flat-square&logoColor=white" alt="Endpoints"/>
<img src="https://img.shields.io/badge/Providers-14-a855f7?style=flat-square&logoColor=white" alt="Providers"/>
<img src="https://img.shields.io/badge/Version-2.3.0-f43f8e?style=flat-square&logoColor=white" alt="Version"/>
<img src="https://img.shields.io/badge/Endpoints-46-6366f1?style=flat-square&logoColor=white" alt="Endpoints"/>
<img src="https://img.shields.io/badge/Providers-12-a855f7?style=flat-square&logoColor=white" alt="Providers"/>
</p>
<p align="center">
<b>A complete RESTful API for anime streaming data powered by AniList GraphQL and Miruro providers</b><br/>
Search, browse, filter, watch — every endpoint returns fresh data with smart caching.<br/>
49 endpoints, 14 streaming providers (12 pipe + 2 direct embed), M3U8 URLs with subtitles, skip timestamps, and batch operations.
46 endpoints, 12 streaming providers, M3U8 URLs with subtitles, skip timestamps, and batch operations.
</p>
<p align="center">
@@ -87,13 +84,13 @@
### Why MiruroAPI?
- 🎬 **49 Endpoints** — Complete anime data coverage
- 🎬 **46 Endpoints** — Complete anime data coverage
- 🔍 **Full-Text Search** — Search anime by keyword with suggestions
- 🎭 **Characters & Voice Actors** — Full character data from AniList
- 🎯 **Advanced Filtering** — Genre, year, season, format, sort
- 🏆 **Trending & Popular** — Discover what's hot right now
- 📅 **Airing Schedule** — See what's airing on any date
- 📡 **14 Streaming Providers** — M3U8 streaming sources
- 📡 **12 Streaming Providers** — M3U8 streaming sources
- ⏭️ **Skip Timestamps** — OP/ED skip data
- 🎭 **Character & Staff Search** — Search characters and staff by name
- 🎬 **Batch Episodes** — Fetch multiple episode sources in parallel
@@ -101,7 +98,7 @@
- 🔒 **CORS Enabled** — Works from any frontend, no proxy needed
- 🚀 **Zero-Config Deploy** — One click to Vercel, or run standalone with Express
### 🔄 Self-Healing System (v2.3.2)
### 🔄 Self-Healing System (v2.3.0)
When Cloudflare blocks streaming requests, the API automatically tries fallback methods:
- **Direct** → Mirror rotation across 4 domains (miruro.to, miruro.ru, miruro.bz, miruro.tv)
@@ -110,35 +107,18 @@ When Cloudflare blocks streaming requests, the API automatically tries fallback
Zero-config: works out of the box. Add `SCRAPER_API_KEY` env var for automatic Cloudflare bypass (requires ScraperAPI premium plan for protected domains).
### Direct Embed Providers (v2.3.3)
Bypass Cloudflare entirely with direct embed URLs from third-party providers — no scraping needed:
- **Megavid** (`megavid.buzz`) — Direct video embed
- **AniXo** (`anixo.buzz`) — Direct video embed, CORS-friendly
```bash
# Get embed URLs for all providers
curl "https://mirurotvapi.vercel.app/api/embed/20/1?lang=sub"
# Get embed URL for specific provider
curl "https://mirurotvapi.vercel.app/api/embed/20/1/anixo?lang=sub"
```
These return iframe-ready URLs that work from any environment — no Cloudflare blocking.
### ⚠️ Streaming Status
> **Streaming endpoints (`/api/episodes`, `/api/watch`, `/api/sources`, `/api/stream`) return 500 errors when Cloudflare blocks pipe requests.**
>
> This is expected behavior on Vercel's free tier. Cloudflare's bot protection blocks requests from datacenter IPs (including Vercel's). Metadata endpoints (search, info, trending, etc.) work perfectly — only streaming is affected.
| What Works | What's Blocked (pipe-based) |
| What Works | What's Blocked |
|:---|:---|
| Search, suggestions, filter | Episodes via pipe (503) |
| Info, characters, relations | Stream/download via pipe (503) |
| Trending, popular, schedule | Watch via pipe (503) |
| **NEW: /api/embed (direct embeds)** | |
| Search, suggestions, filter | Episodes, sources, stream |
| Info, characters, relations | Watch, download |
| Trending, popular, schedule | All pipe-dependent endpoints |
| Genres, tags, calendar | Streaming provider data |
| All AniList-backed endpoints | |
**To fix streaming**, set one of these environment variables in Vercel:
@@ -163,8 +143,7 @@ flowchart TD
C -- MISS --> E{"🔍 Which Source?"}
E -- Metadata --> F["📡 AniList GraphQL<br/>graphql.anilist.co"]
E -- Streaming (pipe) --> G["📺 Miruro Pipe<br/>miruro.{to,ru,bz,tv}/api/secure/pipe"]
E -- Streaming (embed) --> E2["🎬 Direct Embed<br/>megavid.buzz / anixo.buzz"]
E -- Streaming --> G["📺 Miruro Pipe<br/>miruro.{to,ru,bz,tv}/api/secure/pipe"]
F --> H["AniList Response<br/>JSON"]
G --> I["Pipe Response<br/>base64url + gzip"]
@@ -179,7 +158,6 @@ flowchart TD
style E fill:#1e1e2e,stroke:#a855f7,color:#f1f5f9
style F fill:#1e1e2e,stroke:#06b6d4,color:#f1f5f9
style G fill:#1e1e2e,stroke:#eab308,color:#f1f5f9
style E2 fill:#1e1e2e,stroke:#22c55e,color:#f1f5f9
style H fill:#1e1e2e,stroke:#06b6d4,color:#f1f5f9
style I fill:#1e1e2e,stroke:#eab308,color:#f1f5f9
style J fill:#1e1e2e,stroke:#22c55e,color:#f1f5f9
@@ -197,7 +175,7 @@ flowchart TD
- **AniList GraphQL** for rich metadata
- **Miruro pipe** for streaming sources
- **Smart caching** with configurable TTL
- **49 RESTful endpoints**
- **46 RESTful endpoints**
- **Gzip compression** — 30-70% smaller responses
- **Request logging** — method, path, status, duration
- **Graceful error handling** per endpoint
@@ -230,7 +208,6 @@ flowchart TD
- **Sub/Dub** support per provider
- **Codec and fansub** metadata
- **Batch episode fetching** — multiple sources in parallel
- **Direct embed providers** (megavid, anixo) — bypass Cloudflare
- **Quality fallback** — auto 1080p → 720p → 360p
- **Subtitle extraction** — expose subtitle URLs
@@ -253,14 +230,14 @@ flowchart TD
| Feature | Description | Status |
|:---|:---|:---:|
| 🎬 49 API Endpoints | Complete anime data coverage | ✅ |
| 🎬 46 API Endpoints | Complete anime data coverage | ✅ |
| 🔍 Full-Text Search | Keyword search with pagination | ✅ |
| 💡 Search Suggestions | Fast autocomplete | ✅ |
| 🎯 Advanced Filtering | Genre, year, season, format, sort | ✅ |
| 🎭 Characters + Voice Actors | Full character data from AniList | ✅ |
| 🔗 Relations & Recommendations | Related anime discovery | ✅ |
| ⏭️ Skip Timestamps | OP/ED skip data | ✅ |
| 📡 14 Streaming Providers | M3U8 streaming sources | ✅ |
| 📡 12 Streaming Providers | M3U8 streaming sources | ✅ |
| 🔄 Smart Caching | In-memory Map with TTL | ✅ |
| 🚀 One-Click Deploy | Vercel button deployment | ✅ |
| 🏗️ Express Mode | Standalone server with `npm start` | ✅ |
@@ -285,13 +262,6 @@ flowchart TD
| 📺 **Miruro** | `miruro.bz` | Mirror domain |
| 📺 **Miruro** | `miruro.tv` | Mirror domain |
### Direct Embed Sources (no scraping)
| Source | Domain | Data |
|:---|:---|:---|
| 🎬 **Megavid** | `megavid.buzz` | Direct video embed URLs |
| 🎬 **AniXo** | `anixo.buzz` | Direct video embed URLs (CORS-friendly) |
### 🎬 Streaming Providers
| Provider | Provider | Provider | Provider |
@@ -392,7 +362,7 @@ MiruroAPI/
│ │ └── 📄 cache.js # 💾 In-memory cache with TTL
│ │
│ └── 📂 routes/ # 🛤️ Express routes
│ └── 📄 apiRoutes.js # 🌐 Main API routes (49 endpoints)
│ └── 📄 apiRoutes.js # 🌐 Main API routes (46 endpoints)
│
├── 📄 server.js # 🚀 Express server entry point
├── 📄 package.json # 📦 Dependencies & scripts
@@ -563,15 +533,14 @@ console.log(resp.data);
"success": true,
"results": {
"status": "healthy",
"version": "2.3.3",
"version": "2.3.0",
"uptime": "0h 0m 34s",
"uptimeSeconds": 34,
"timestamp": "2026-06-09T09:55:00.884Z",
"node": "v24.14.1",
"memory": { "used": "13MB", "total": "15MB" },
"endpoints": 49,
"providers": ["kiwi","pewe","bee","bonk","bun","ally","nun","twin","cog","moo","hop","telli"],
"embedProviders": ["megavid", "anixo"]
"endpoints": 46,
"providers": ["kiwi","pewe","bee","bonk","bun","ally","nun","twin","cog","moo","hop","telli"]
}
}
```
@@ -611,7 +580,7 @@ console.log(resp.data);
"uptime": "0h 0m 34s",
"requests": { "total": 156, "errors": 3, "successRate": "98.1%" },
"cache": { "size": 12, "maxSize": 100, "ttl": "1 min" },
"endpoints": 49,
"endpoints": 46,
"timestamp": "2026-06-09T09:55:00.884Z"
}
}
@@ -1445,10 +1414,9 @@ docker build -t miruroapi .
docker run -p 3000:3000 miruroapi
```
### ⚡ Cloudflare Worker (Experimental)
### ⚡ Cloudflare Worker (Streaming Works!)
> **Cloudflare Worker implementation** — runs the API on Cloudflare's edge network.
> Streaming endpoints *may* work via edge-to-edge requests, but this is **untested and not guaranteed**.
> **Streaming endpoints work on Workers** — requests to Miruro bypass Cloudflare bot detection because Workers run on Cloudflare's edge network (edge-to-edge traffic is trusted).
```bash
# Install wrangler CLI
@@ -1457,19 +1425,20 @@ npm install -g wrangler
# Login to Cloudflare
wrangler login
# Deploy worker
# Deploy
wrangler deploy worker.js --name miruroapi
```
**Theory:** Cloudflare Workers run on Cloudflare's edge, so requests to `miruro.to` *should* be trusted (edge-to-edge). However, this has not been verified in production.
Your API is live at: `https://miruroapi.YOUR_SUBDOMAIN.workers.dev`
**Features:** Zero cold starts, 100K free requests/day, no npm dependencies, 15 API endpoints.
| Feature | Vercel | Cloudflare Worker |
|---------|--------|-------------------|
| Streaming (pipe) | Blocked (403) | Works |
| Cold starts | ~500ms | Zero |
| Free tier | Unlimited | 100K req/day |
| npm deps | Yes | No (native fetch) |
**Optional env vars:**
```bash
wrangler secret put SCRAPER_API_KEY # ScraperAPI premium key ($49/mo)
wrangler secret put FLARESOLVERR_URL # FlareSolverr Docker URL
```
> See `worker.js` for the full Worker implementation with 15 endpoints.
---
@@ -1508,8 +1477,7 @@ wrangler secret put FLARESOLVERR_URL # FlareSolverr Docker URL
| Version | Date | Key Changes |
|:---|:---|:---|
| **2.3.2** | 2026-08-05 | Cloudflare Worker implementation — edge-to-edge streaming bypass, wrangler.toml, 15 Worker endpoints |
| **2.3.1** | 2026-07-02 | Self-healing fallback system — multi-method pipe recovery, /api/pipe-health endpoint, ScraperAPI + FlareSolverr support |
| **2.3.0** | 2026-07-02 | Self-healing fallback system — multi-method pipe recovery, /api/pipe-health endpoint, ScraperAPI + FlareSolverr support |
| **2.1.4** | 2026-06-25 | Security hardening — XOR keys to env vars, CORS restriction, input sanitization, bug fixes (multi-search, deepTranslate, getBestStream) |
| **2.1.3** | 2026-06-25 | Diagnostic sweep — tags, random, character/staff 404 fixes, pipe retry with backoff, security headers |
| **2.1.2** | 2026-06-25 | Streaming architecture — pru proxy decode, CDN CORS proxy, raw stream URLs |
@@ -1550,9 +1518,42 @@ Cloudflare detects:
- Missing browser fingerprint headers
- Non-residential traffic patterns
#### Fix 1: FlareSolverr (Best — Free, Works!)
#### Fix 1: Cloudflare Worker (Best — Free, Works!)
FlareSolverr runs a headless browser that bypasses Cloudflare automatically. This is the most reliable fix.
Workers run on Cloudflare's edge network. Edge-to-edge requests are trusted — no bot detection.
```bash
# 1. Install wrangler
npm install -g wrangler
# 2. Login
wrangler login
# 3. Deploy
wrangler deploy worker.js --name miruroapi
# 4. Your streaming now works at:
# https://miruroapi.YOUR_SUBDOMAIN.workers.dev/api/episodes/20
```
**Cost:** Free (100K requests/day). No API key needed.
#### Fix 2: ScraperAPI (Paid — $49/month)
ScraperAPI proxies your requests through residential IPs, bypassing Cloudflare.
```bash
# 1. Sign up at https://www.scraperapi.com/ (free 1,000 requests/month)
# 2. Get your API key from the dashboard
# 3. Add to Vercel environment variables:
SCRAPER_API_KEY=your_api_key_here
```
**Cost:** $49/month (premium plan required for protected domains like Miruro).
#### Fix 3: FlareSolverr (Self-Hosted — Unlimited)
FlareSolverr runs a headless browser that bypasses Cloudflare automatically.
```bash
# 1. Run FlareSolverr on your own server
@@ -1569,22 +1570,9 @@ FLARESOLVERR_URL=http://your-server-ip:8191
- Public IP address
- Docker installed
#### Fix 2: ScraperAPI (Paid — $49/month)
#### Fix 4: Self-Host the Entire API
ScraperAPI proxies your requests through residential IPs, bypassing Cloudflare.
```bash
# 1. Sign up at https://www.scraperapi.com/
# 2. Get your API key from the dashboard
# 3. Add to Vercel environment variables:
SCRAPER_API_KEY=your_api_key_here
```
**Cost:** $49/month (premium plan required for protected domains like Miruro).
#### Fix 3: Self-Host the Entire API
If you run the API on your own VPS (not Vercel), requests come from your IP which may not be blocked.
If you run the API on your own VPS (not Vercel), requests come from your residential/datacenter IP which may not be blocked.
```bash
# Clone and install
@@ -1623,49 +1611,6 @@ Expected response when streaming works:
}
```
### 🤝 Contributing Bypass Solutions
Streaming is blocked on all cloud platforms. We need help! If you can implement any of these, PRs are welcome:
#### Free / Open Source (Best for self-hosted)
| Tool | Language | How It Works | Notes |
|------|----------|-------------|-------|
| [nodriver](https://github.com/ultrafunkamsterdam/nodriver) | Python | CDP-based browser automation, bypasses automation detection | Successor to undetected-chromedriver |
| [Camoufox](https://github.com/nicandris/camoufox) | Python | Stealth Firefox with fingerprint spoofing | 82% baseline Turnstile bypass |
| [curl_cffi](https://github.com/nicandris/curl_cffi) | Python | TLS fingerprint impersonation | Lightweight, no browser needed |
| [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr) | Docker | Headless Chrome proxy server | Can't solve interactive CAPTCHAs |
| [patchright](https://github.com/nicandris/patchright) | Python | Patched Playwright fork | Drop-in Playwright replacement |
| [scrapy-impersonate](https://github.com/driver-utils/scrapy-impersonate) | Python | Scrapy with TLS impersonation | For Scrapy users |
#### Paid APIs (Good for cloud deployments)
| Service | Free Tier | Price | API Example |
|---------|-----------|-------|-------------|
| [Scrape.do](https://scrape.do) | 1,000 credits/mo | $0.08/1K | `https://api.scrape.do/?url=...&token=...` |
| [ZenRows](https://zenrows.com) | Trial | $49/mo | `https://api.zenrows.com/?url=...&apikey=...` |
| [ScrapingBee](https://scrapingbee.com) | Trial | $49/mo | `https://app.scrapingbee.com/api/v1/?...` |
#### What to Implement
1. **Node.js integration** — Add `nodriver` or `Camoufox` as a pipe fallback in `src/helpers/pipe.js`
2. **Scrape.do adapter** — Simple HTTP proxy, 1K free credits/mo, no browser needed
3. **Playwright stealth mode** — Use `patchright` or `playwright-extra-stealth` for browser-based bypass
4. **Residential proxy support** — Add proxy rotation for pipe requests
#### How to Contribute
1. Fork the repo
2. Add your bypass method as a new fallback in `src/helpers/pipe.js`
3. Add your API key to `.env.example`
4. Test with: `curl http://localhost:3000/api/episodes/20`
5. Submit a PR
```bash
# Example: Add Scrape.do as a fallback
SCRAPE_DO_TOKEN=your_token_here
```
### 🐛 Debug Mode
```bash
@@ -1721,13 +1666,7 @@ Yes! Use <code>npm start</code> to run the Express server on any VPS, Docker con
<details>
<summary><b>🎬 Which streaming providers are available?</b></summary>
<br/>
12 pipe providers: kiwi, pewe, bee, bonk, bun, ally, nun, twin, cog, moo, hop, telli + 2 direct embed providers: megavid, anixo. Not all anime are available on every provider.
</details>
<details>
<summary><b>🎬 How do I use the direct embed providers?</b></summary>
<br/>
Use <code>/api/embed/:anilistId/:episode</code> to get iframe-ready URLs from megavid.buzz and anixo.buzZ. These bypass Cloudflare entirely — no scraping needed. Example: <code>GET /api/embed/20/1?lang=sub</code> returns URLs you can embed directly in an iframe.
12 providers: kiwi, pewe, bee, bonk, bun, ally, nun, twin, cog, moo, hop, telli. Not all anime are available on every provider.
</details>
<details>
@@ -1765,14 +1704,14 @@ Use <code>/api/pipe-health</code> to check which pipe methods are working. It sh
### ✅ Completed
- [x] 🎬 49 API endpoints covering all data
- [x] 🎬 46 API endpoints covering all data
- [x] 🔍 Full-text search with pagination
- [x] 💡 Search suggestions for autocomplete
- [x] 🎯 Advanced filtering (genre, year, season, format, sort)
- [x] 🎭 Characters + voice actors from AniList
- [x] 🔗 Relations and recommendations
- [x] ⏭️ Skip timestamps (OP/ED)
- [x] 📡 14 streaming providers with M3U8 URLs
- [x] 📡 12 streaming providers with M3U8 URLs
- [x] 🔄 Smart caching with configurable TTL
- [x] 🚀 One-click Vercel deployment
- [x] 🐳 Docker support
@@ -1780,8 +1719,6 @@ Use <code>/api/pipe-health</code> to check which pipe methods are working. It sh
- [x] 📖 Comprehensive documentation with real API data
- [x] 🔒 Security hardening (XOR keys in env vars, CORS restriction, input sanitization)
- [x] 🛡️ Security headers at CDN level
- [x] 🎬 49 API endpoints with direct embed providers
- [x] 🎬 Direct embed providers (megavid, anixo) bypass Cloudflare
---
+48
View File
@@ -5,6 +5,7 @@
```
MiruroAPI/
├── server.js # Express entry point, port 3000
├── worker.js # Cloudflare Worker edition (edge runtime)
├── package.json # name: "miruro-api"
├── vercel.json # Routes /api/* and /* to server.js
├── Dockerfile # Docker support (node:20-alpine)
@@ -199,4 +200,51 @@ Error responses:
| cors | ^2.8.5 | CORS headers |
| dotenv | ^16.4.0 | Environment variables |
## Cloudflare Worker Architecture (v2.3.1)
The Worker edition (`worker.js`) runs the same API on Cloudflare's edge network. Key difference: **streaming endpoints work** because edge-to-edge requests bypass Cloudflare bot detection.
### Why Workers Work
```
Vercel (datacenter IP) → miruro.to → Cloudflare WAF → 403 Blocked
Cloudflare Worker (edge) → miruro.to → Cloudflare internal network → 200 OK
```
### Worker Runtime Differences
| Feature | Express (Node.js) | Cloudflare Worker |
|---------|-------------------|-------------------|
| HTTP client | axios | native fetch |
| Decompression | zlib (Node.js) | DecompressionStream (Web API) |
| Caching | in-memory Map | Workers KV (optional) |
| npm packages | Yes | No (bundle only) |
| Cold starts | ~500ms | Zero |
| CPU limit | None | 10ms (free) / 30s (paid) |
### Worker Endpoints (15)
| Endpoint | Description |
|----------|-------------|
| `GET /api/health` | System health check |
| `GET /api/search?query=` | Full-text anime search |
| `GET /api/suggestions?query=` | Autocomplete suggestions |
| `GET /api/filter?genre=` | Advanced filter |
| `GET /api/trending` | Trending anime |
| `GET /api/popular` | Most popular |
| `GET /api/top` | Top scored |
| `GET /api/random` | Random anime |
| `GET /api/info/:id` | Full anime info |
| `GET /api/anime/:id/characters` | Characters + voice actors |
| `GET /api/anime/:id/relations` | Related anime |
| `GET /api/genres` | All genres |
| `GET /api/episodes/:id` | Episode list (pipe) |
| `GET /api/watch/:provider/:anilistId/:category/:slug` | Streaming sources (pipe) |
### Deploy
```bash
npm install -g wrangler
wrangler login
wrangler deploy worker.js --name miruroapi
```
+1 -50
View File
@@ -1,6 +1,6 @@
# API Endpoints Reference
Complete documentation for all 49 MiruroAPI v2.3.3 endpoints.
Complete documentation for all 45 MiruroAPI v2.3.0 endpoints.
---
@@ -57,11 +57,6 @@ Complete documentation for all 49 MiruroAPI v2.3.3 endpoints.
- [Download URL](#download-url)
- [Watch (Streaming)](#watch-streaming)
### Direct Embed (no scraping)
- [Embed All Providers](#embed-all-providers)
- [Embed Specific Provider](#embed-specific-provider)
- [Embed Providers List](#embed-providers-list)
### Utility
- [Compare](#compare)
- [Genre Stats](#genre-stats)
@@ -581,50 +576,6 @@ Anime release timeline with prequels, sequels, spin-offs.
---
## Embed All Providers
```
GET /api/embed/:anilistId/:episode
```
Returns iframe-ready embed URLs for all available providers. No scraping required.
| Param | Type | Default | Description |
|-------|------|---------|-------------|
| anilistId | int | required | AniList anime ID |
| episode | int | required | Episode number |
| lang | string | sub | Language: `sub` or `dub` |
| malId | int | optional | MAL ID (uses MAL-based URLs if provided) |
---
## Embed Specific Provider
```
GET /api/embed/:anilistId/:episode/:provider
```
Returns embed URL for a specific provider.
| Param | Type | Default | Description |
|-------|------|---------|-------------|
| anilistId | int | required | AniList anime ID |
| episode | int | required | Episode number |
| provider | string | required | Provider ID: `megavid` or `anixo` |
| lang | string | sub | Language: `sub` or `dub` |
---
## Embed Providers List
```
GET /api/embed-providers
```
Lists all available embed providers with example URLs.
---
## Health Check
```
+3 -29
View File
@@ -5,39 +5,13 @@ This guide explains how to get streaming URLs from MiruroAPI in 3 steps.
## Overview
```
Option A (Pipe-based — may be Cloudflare-blocked):
Step 1: Get Episodes → /api/episodes/:anilistId
Step 2: Get Stream URLs → /api/watch/:provider/:anilistId/:category/:slug
Step 3: Play M3U8 → Use HLS player
Option B (Direct embed — no Cloudflare blocking):
Step 1: Get Embed URLs → /api/embed/:anilistId/:episode
Step 2: Embed in iframe → <iframe src="..."></iframe>
Step 1: Get Episodes → /api/episodes/:anilistId
Step 2: Get Stream URLs → /api/watch/:provider/:anilistId/:category/:slug
Step 3: Play M3U8 → Use HLS player
```
---
## Alternative: Direct Embed (Recommended)
If pipe-based streaming is blocked by Cloudflare, use direct embed providers instead:
```bash
# Step 1: Get embed URLs for all providers
curl "https://mirurotvapi.vercel.app/api/embed/20/1?lang=sub"
# Step 2: Embed the URL in an iframe
<iframe src="https://anixo.buzz/embed/ani/20/1/sub" allow="autoplay; fullscreen"></iframe>
```
| Provider | URL Pattern | CORS |
|----------|-------------|------|
| Megavid | `megavid.buzz/ani/{id}/{ep}/{lang}` | No |
| AniXo | `anixo.buzz/embed/ani/{id}/{ep}/{lang}` | Yes |
This approach bypasses Cloudflare entirely — no scraping, no proxy, works from any environment.
---
## Step 1: Get Episodes
Each anime has episodes from multiple providers. Fetch the episode list with the AniList ID.
+1466 -3
View File
File diff suppressed because it is too large Load Diff
+6 -3
View File
@@ -1,12 +1,14 @@
{
"name": "miruro-api",
"version": "2.3.3",
"version": "2.3.0",
"description": "RESTful API for Miruro - Anime streaming data powered by AniList GraphQL and Miruro streaming providers",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"test": "node test.js"
"test": "node test.js",
"deploy": "wrangler deploy",
"preview": "wrangler dev"
},
"dependencies": {
"axios": "^1.8.0",
@@ -16,7 +18,8 @@
"express": "^4.21.0"
},
"devDependencies": {
"nodemon": "^3.1.0"
"nodemon": "^3.1.0",
"wrangler": "^4.106.0"
},
"keywords": [
"anime",
+2 -4
View File
@@ -101,12 +101,10 @@ app.use((req, res, next) => {
return res.status(403).json({ success: false, message: "Origin not allowed" });
}
} else {
// Default: allow all origins for public API usage by anime frontends
res.setHeader("Access-Control-Allow-Origin", origin || "*");
}
res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-No-Compression, Authorization");
res.setHeader("Access-Control-Expose-Headers", "X-RateLimit-Limit, X-RateLimit-Remaining");
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-No-Compression");
res.setHeader("Access-Control-Max-Age", "86400");
if (req.method === "OPTIONS") {
@@ -303,7 +301,7 @@ app.use((req, res) => {
if (!process.env.VERCEL) {
app.listen(PORT, () => {
console.log(`[${new Date().toISOString()}] MiruroAPI v2.3.3 listening at ${PORT}`);
console.log(`[${new Date().toISOString()}] MiruroAPI v2.3.0 listening at ${PORT}`);
});
}
+5 -65
View File
@@ -113,7 +113,7 @@ const methodDirect = async (encodedReq) => {
try {
const res = await axios.get(`${origin}${PIPE_PATH}?e=${encodedReq}`, {
headers: HEADERS,
timeout: 15000,
timeout: 20000,
maxRedirects: 5,
});
@@ -126,10 +126,6 @@ const methodDirect = async (encodedReq) => {
lastError = e;
const status = e.response?.status;
if (status === 403 || status === 444) {
throw new Error(`Cloudflare is blocking requests to ${origin} (HTTP ${status}). Configure SCRAPER_API_KEY or FLARESOLVERR_URL for fallback.`);
}
if (
status &&
status >= 400 &&
@@ -140,17 +136,13 @@ const methodDirect = async (encodedReq) => {
throw new Error(`Pipe request failed with status ${status}`);
}
if (e.code === "ECONNABORTED" || e.code === "ETIMEDOUT") {
lastError = new Error(`Request to ${origin} timed out after 15s`);
}
if (attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000;
await new Promise((r) => setTimeout(r, delay));
}
}
}
throw lastError || new Error("Direct request failed after all retries. The streaming provider may be temporarily unavailable.");
throw lastError || new Error("Direct request failed after all retries");
};
// ══════════════════════════════════════════════════════════════
@@ -228,13 +220,10 @@ const pipeRequest = async (path, query) => {
const encodedReq = encodePipeRequest(payload);
const errors = [];
const availableMethods = METHODS.filter((m) => !m.requires || m.requires());
if (availableMethods.length === 0) {
throw new Error("No streaming methods configured. Set SCRAPER_API_KEY or FLARESOLVERR_URL in your .env file to enable fallback methods.");
}
for (const method of METHODS) {
if (method.requires && !method.requires()) continue;
for (const method of availableMethods) {
try {
const result = await method.fn(encodedReq);
return result.data;
@@ -243,14 +232,8 @@ const pipeRequest = async (path, query) => {
}
}
const isCloudflare = errors.some((e) => e.error.includes("Cloudflare") || e.error.includes("403"));
const baseMessage = isCloudflare
? "Cloudflare is blocking all streaming requests"
: "All streaming methods failed";
throw new Error(
`${baseMessage}: ${errors.map((e) => `${e.method}(${e.error})`).join(", ")}. ` +
"Configure SCRAPER_API_KEY or FLARESOLVERR_URL for additional fallback methods."
`All pipe methods failed: ${errors.map((e) => `${e.method}(${e.error})`).join(", ")}`
);
};
@@ -592,46 +575,6 @@ const getBatchSources = async (provider, anilistId, category, slugs) => {
};
};
// ══════════════════════════════════════════════════════════════
// DIRECT EMBED PROVIDERS (bypass Cloudflare — no scraping needed)
// ══════════════════════════════════════════════════════════════
const EMBED_PROVIDERS = [
{
id: "megavid",
name: "Megavid",
cors: false,
makeUrl(anilistId, episode, lang = "sub", malId = null) {
const idType = malId ? "mal" : "ani";
const id = malId || anilistId;
return `https://megavid.buzz/${idType}/${id}/${episode}/${lang}?autoplay=true`;
},
},
{
id: "anixo",
name: "AniXo",
cors: true,
makeUrl(anilistId, episode, lang = "sub") {
return `https://anixo.buzz/embed/ani/${anilistId}/${episode}/${lang}?autoplay=true`;
},
},
];
const getEmbedUrl = (providerId, anilistId, episode, lang = "sub", malId = null) => {
const provider = EMBED_PROVIDERS.find((p) => p.id === providerId);
if (!provider) return null;
return provider.makeUrl(anilistId, episode, lang, malId);
};
const getEmbedUrls = (anilistId, episode, lang = "sub", malId = null) => {
return EMBED_PROVIDERS.map((p) => ({
id: p.id,
name: p.name,
cors: p.cors,
url: p.makeUrl(anilistId, episode, lang, malId),
}));
};
module.exports = {
getEpisodes,
getSources,
@@ -647,9 +590,6 @@ module.exports = {
deepTranslate,
injectSourceSlugs,
pipeHealthCheck,
EMBED_PROVIDERS,
getEmbedUrl,
getEmbedUrls,
};
// ══════════════════════════════════════════════════════════════ END: pipe.js
+13 -142
View File
@@ -678,12 +678,7 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
jsonResponse(res, result);
} catch (err) {
const isBlocked = err.message.includes("Cloudflare") || err.message.includes("blocked") || err.message.includes("403");
const status = isBlocked ? 503 : err.message.includes("not found") ? 404 : 500;
const message = isBlocked
? "Streaming provider is blocking requests (Cloudflare protection). Try again later or configure SCRAPER_API_KEY / FLARESOLVERR_URL."
: err.message;
jsonError(res, message, status);
jsonError(res, err.message);
}
});
@@ -704,7 +699,7 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
}
});
// ---- FEATURE: Streaming sources (query-based endpoint) ----
// ---- FEATURE: Streaming sources (detailed/manual endpoint) ----
router.get("/sources", async (req, res) => {
try {
const { episodeId, provider, anilistId, category = "sub" } = req.query;
@@ -715,27 +710,7 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
const result = await pipe.getSources(episodeId, provider, parseInt(anilistId), category);
jsonResponse(res, result);
} catch (err) {
const status = err.message.includes("Cloudflare") || err.message.includes("blocked")
? 503 : err.message.includes("not found") ? 404 : 500;
jsonError(res, err.message, status);
}
});
// ---- FEATURE: Streaming sources by episode ID (path-based endpoint) ----
router.get("/sources/:id", async (req, res) => {
try {
const { id } = req.params;
const { provider, anilistId, category = "sub" } = req.query;
if (!provider || !anilistId) {
return jsonError(res, "provider and anilistId query params are required", 400);
}
const result = await pipe.getSources(id, provider, parseInt(anilistId), category);
jsonResponse(res, result);
} catch (err) {
const status = err.message.includes("Cloudflare") || err.message.includes("blocked")
? 503 : err.message.includes("not found") ? 404 : 500;
jsonError(res, err.message, status);
jsonError(res, err.message);
}
});
@@ -760,12 +735,7 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
skipTimes,
});
} catch (err) {
const isBlocked = err.message.includes("Cloudflare") || err.message.includes("blocked") || err.message.includes("403");
const status = isBlocked ? 503 : err.message.includes("not found") ? 404 : 500;
const message = isBlocked
? "Streaming provider is blocking requests (Cloudflare protection). Try again later or configure SCRAPER_API_KEY / FLARESOLVERR_URL."
: err.message;
jsonError(res, message, status);
jsonError(res, err.message);
}
});
@@ -780,12 +750,7 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
const result = await pipe.getDownloadUrl(provider, parseInt(anilistId), category, slug);
jsonResponse(res, result);
} catch (err) {
const isBlocked = err.message.includes("Cloudflare") || err.message.includes("blocked") || err.message.includes("403");
const status = isBlocked ? 503 : err.message.includes("not found") ? 404 : 500;
const message = isBlocked
? "Streaming provider is blocking requests (Cloudflare protection). Try again later or configure SCRAPER_API_KEY / FLARESOLVERR_URL."
: err.message;
jsonError(res, message, status);
jsonError(res, err.message);
}
});
@@ -803,94 +768,10 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
skipTimes,
});
} catch (err) {
const isBlocked = err.message.includes("Cloudflare") || err.message.includes("blocked") || err.message.includes("403");
const status = isBlocked ? 503 : err.message.includes("not found") ? 404 : 500;
const message = isBlocked
? "Streaming provider is blocking requests (Cloudflare protection). Try again later or configure SCRAPER_API_KEY / FLARESOLVERR_URL."
: err.message;
jsonError(res, message, status);
jsonError(res, err.message);
}
});
// ══════════════════════════════════════════════════════════════
// DIRECT EMBED PROVIDERS (no scraping — bypasses Cloudflare)
// ══════════════════════════════════════════════════════════════
// ---- FEATURE: Direct embed URLs for all providers ----
router.get("/embed/:anilistId/:episode", (req, res) => {
try {
const anilistId = parseInt(req.params.anilistId);
const episode = parseInt(req.params.episode);
if (isNaN(anilistId) || anilistId <= 0) return jsonError(res, "Invalid AniList ID", 400);
if (isNaN(episode) || episode <= 0) return jsonError(res, "Invalid episode number", 400);
const { lang = "sub", malId } = req.query;
const validLangs = ["sub", "dub"];
if (!validLangs.includes(lang)) return jsonError(res, "lang must be 'sub' or 'dub'", 400);
const embeds = pipe.getEmbedUrls(anilistId, episode, lang, malId ? parseInt(malId) : null);
jsonResponse(res, {
anilistId,
episode,
lang,
providers: embeds,
total: embeds.length,
description: "Direct embed URLs — embed in an iframe to watch. No scraping required.",
});
} catch (err) {
jsonError(res, err.message, 500);
}
});
// ---- FEATURE: Direct embed URL for specific provider ----
router.get("/embed/:anilistId/:episode/:provider", (req, res) => {
try {
const anilistId = parseInt(req.params.anilistId);
const episode = parseInt(req.params.episode);
const providerId = req.params.provider;
if (isNaN(anilistId) || anilistId <= 0) return jsonError(res, "Invalid AniList ID", 400);
if (isNaN(episode) || episode <= 0) return jsonError(res, "Invalid episode number", 400);
const { lang = "sub", malId } = req.query;
const validLangs = ["sub", "dub"];
if (!validLangs.includes(lang)) return jsonError(res, "lang must be 'sub' or 'dub'", 400);
const url = pipe.getEmbedUrl(providerId, anilistId, episode, lang, malId ? parseInt(malId) : null);
if (!url) return jsonError(res, `Unknown provider: ${providerId}. Available: megavid, anixo`, 404);
const provider = pipe.EMBED_PROVIDERS.find((p) => p.id === providerId);
jsonResponse(res, {
anilistId,
episode,
lang,
provider: providerId,
name: provider.name,
url,
cors: provider.cors,
description: "Embed this URL in an iframe to watch. No scraping required.",
});
} catch (err) {
jsonError(res, err.message, 500);
}
});
// ---- FEATURE: List all available embed providers ----
router.get("/embed-providers", (req, res) => {
jsonResponse(res, {
providers: pipe.EMBED_PROVIDERS.map((p) => ({
id: p.id,
name: p.name,
cors: p.cors,
example: p.makeUrl(20, 1, "sub"),
})),
total: pipe.EMBED_PROVIDERS.length,
usage: "GET /api/embed/:anilistId/:episode?lang=sub",
description: "Direct embed providers — bypass Cloudflare by embedding video URLs directly in an iframe.",
});
});
// ══════════════════════════════════════════════════════════════
// UTILITY FEATURES
// ══════════════════════════════════════════════════════════════
@@ -1243,7 +1124,7 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
success: true,
results: {
status: "healthy",
version: "2.3.3",
version: "2.3.0",
uptime: `${hours}h ${minutes}m ${seconds}s`,
uptimeSeconds: uptime,
timestamp: new Date().toISOString(),
@@ -1257,9 +1138,8 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
size: require("../helpers/cache").getCacheSize(),
maxSize: 100,
},
endpoints: 49,
endpoints: 46,
providers: ["kiwi", "pewe", "bee", "bonk", "bun", "ally", "nun", "twin", "cog", "moo", "hop", "telli"],
embedProviders: ["megavid", "anixo"],
},
});
});
@@ -1274,9 +1154,6 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
const failed = Object.entries(results).filter(
([, v]) => v.status === "failed"
);
const skipped = Object.entries(results).filter(
([, v]) => v.status === "skipped"
);
res.json({
success: true,
@@ -1284,11 +1161,10 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
methods: results,
working: working.length,
failed: failed.length,
skipped: skipped.length,
total: Object.keys(results).length,
recommendation:
working.length === 0
? "All methods blocked or unconfigured. Set SCRAPER_API_KEY or FLARESOLVERR_URL in .env to enable fallback methods. See .env.example for details."
? "All methods blocked. Check Cloudflare status or add SCRAPER_API_KEY / FLARESOLVERR_URL."
: working[0]
? `Preferred: ${working[0][0]} (${working[0][1].latency})`
: "No methods available",
@@ -1296,7 +1172,7 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
},
});
} catch (err) {
jsonError(res, err.message, 500);
jsonError(res, err.message);
}
});
@@ -1322,8 +1198,7 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
currentSize: require("../helpers/cache").getCacheSize(),
description: "Map-based cache with TTL expiration and FIFO eviction",
},
endpoints: 49,
embedProviders: 2,
endpoints: 46,
timestamp: new Date().toISOString(),
},
});
@@ -1336,7 +1211,7 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
info: {
title: "MiruroAPI",
description: "Free REST API for anime streaming data — AniList GraphQL + Miruro streaming providers",
version: "2.3.3",
version: "2.3.0",
contact: { name: "Shineii86", url: "https://github.com/Shineii86/MiruroAPI" },
},
servers: [
@@ -1375,8 +1250,7 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
"/anime/{id}/recommendations": { get: { summary: "Recommendations", tags: ["Anime Details"] } },
"/episodes/{id}": { get: { summary: "Episode list", tags: ["Streaming & Downloading"] } },
"/episodes/{id}/batch": { get: { summary: "Batch episode sources", tags: ["Streaming & Downloading"] } },
"/sources": { get: { summary: "Streaming sources (query-based)", tags: ["Streaming & Downloading"] } },
"/sources/{id}": { get: { summary: "Streaming sources by episode ID", tags: ["Streaming & Downloading"] } },
"/sources": { get: { summary: "Streaming sources (detailed)", tags: ["Streaming & Downloading"] } },
"/stream": { get: { summary: "Stream with quality fallback", tags: ["Streaming & Downloading"] } },
"/download": { get: { summary: "Download URL", tags: ["Streaming & Downloading"] } },
"/watch/{provider}/{anilistId}/{category}/{slug}": { get: { summary: "Streaming sources (simple)", tags: ["Streaming & Downloading"] } },
@@ -1385,9 +1259,6 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
"/calendar": { get: { summary: "Monthly calendar", tags: ["Utility"] } },
"/timeline/{id}": { get: { summary: "Anime timeline", tags: ["Utility"] } },
"/providers": { get: { summary: "Provider capabilities", tags: ["System"] } },
"/embed/{anilistId}/{episode}": { get: { summary: "Direct embed URLs (all providers)", tags: ["Streaming & Downloading"] } },
"/embed/{anilistId}/{episode}/{provider}": { get: { summary: "Direct embed URL (specific provider)", tags: ["Streaming & Downloading"] } },
"/embed-providers": { get: { summary: "List embed providers", tags: ["System"] } },
"/health": { get: { summary: "Health check", tags: ["System"] } },
"/stats": { get: { summary: "API statistics", tags: ["System"] } },
"/openapi": { get: { summary: "OpenAPI spec", tags: ["System"] } },
+1 -9
View File
@@ -82,8 +82,6 @@ const tests = [
// ---- FEATURE: Streaming endpoints ----
{ name: "Episodes", url: "/episodes/20" },
{ name: "Batch Episodes", url: "/episodes/20/batch?provider=kiwi&category=sub&slugs=animepahe-1,animepahe-2" },
{ name: "Sources (query)", url: "/sources?episodeId=test&provider=kiwi&anilistId=20" },
{ name: "Sources (path)", url: "/sources/test?provider=kiwi&anilistId=20" },
{ name: "Stream", url: "/stream?provider=kiwi&anilistId=20&category=sub&slug=animepahe-1" },
{ name: "Download", url: "/download?provider=kiwi&anilistId=20&category=sub&slug=animepahe-1" },
{ name: "Watch", url: "/watch/kiwi/20/sub/animepahe-1" },
@@ -93,12 +91,6 @@ const tests = [
{ name: "Genre Stats", url: "/stats/genre" },
{ name: "Calendar", url: "/calendar" },
{ name: "Timeline", url: "/timeline/20" },
// ---- FEATURE: Direct embed endpoints (no scraping) ----
{ name: "Embed All Providers", url: "/embed/20/1?lang=sub" },
{ name: "Embed Megavid", url: "/embed/20/1/megavid?lang=sub" },
{ name: "Embed AniXo", url: "/embed/20/1/anixo?lang=sub" },
{ name: "Embed Providers List", url: "/embed-providers" },
];
// ══════════════════════════════════════════════════════════════
@@ -160,7 +152,7 @@ async function runTest(test) {
* API_URL=http://localhost:3000/api node test.js # Run against local
*/
async function runAll() {
console.log(`\n🧪 MiruroAPI v2.3.3 — Running ${tests.length} tests...\n`);
console.log(`\n🧪 MiruroAPI v2.3.0 — Running ${tests.length} tests...\n`);
// NOTE: Sequential execution — parallel requests would hit rate limiter
for (const test of tests) {
+663 -1498
View File
File diff suppressed because it is too large Load Diff
+14
View File
@@ -0,0 +1,14 @@
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "miruroapi",
"compatibility_date": "2026-07-02",
"observability": {
"enabled": true
},
"assets": {
"directory": "public"
},
"compatibility_flags": [
"nodejs_compat"
]
}
-22
View File
@@ -1,22 +0,0 @@
name = "miruroapi"
main = "worker.js"
compatibility_date = "2024-01-01"
# Cloudflare Worker configuration for MiruroAPI
# Deploy with: wrangler deploy
# Environment variables (set via wrangler secret put or dashboard)
# SCRAPER_API_KEY = "" # ScraperAPI premium key (optional, $49/mo)
# FLARESOLVERR_URL = "" # FlareSolverr Docker instance URL (optional)
# Route configuration (optional — for custom domains)
# routes = [
# { pattern = "api.your-domain.com/api/*", zone_name = "your-domain.com" }
# ]
# Limits
[limits]
cpu_ms = 30000
# Compatibility flags
compatibility_flags = ["nodejs_compat"]