16 Commits
Author SHA1 Message Date
Shinei Nouzen 78029e369b docs: update README, endpoints, streaming, and contributing docs
- Updated version to 2.3.3, endpoints 46→49, providers 12→14
- Added Direct Embed Providers section to README (megavid, anixo)
- Updated streaming status table with embed alternative
- Updated mermaid architecture diagram with embed flow
- Updated health/stats sample responses with new fields
- Added embed endpoint docs to docs/endpoints.md
- Added Direct Embed section to docs/streaming.md
- Updated CONTRIBUTING.md with embed provider notes
- Added FAQ about direct embed providers
- Updated roadmap with completed items
2026-08-25 07:12:51 +05:30
Shinei Nouzen a16a3be096 feat: add direct embed providers (megavid, anixo) to bypass Cloudflare
- Added GET /api/embed/:anilistId/:episode — embed URLs for all providers
- Added GET /api/embed/:anilistId/:episode/:provider — specific provider embed
- Added GET /api/embed-providers — list all available embed providers
- Embed providers return iframe-ready URLs, no scraping required
- Supports sub/dub language selection and optional malId param
- Updated OpenAPI spec with new embed endpoints
- Updated health/stats to show 49 endpoints and embed providers
- Added embed provider tests to test.js

Cloudflare-protected pipe endpoints now have working alternatives:
use /api/embed to get direct video URLs from megavid.buzz and anixo.buzz
2026-08-25 06:56:29 +05:30
Shinei Nouzen a5fbace9c1 fix: resolve Issue #3 — API stability, error handling & new endpoints
- Added /api/sources/:id path-based endpoint for streaming sources
- Improved Cloudflare detection with proper 503 status codes
- Added timeout handling (15s) with exponential backoff for pipe requests
- Enhanced CORS with Authorization header and rate limit expose headers
- All streaming endpoints now return descriptive error messages
- Pipe health check reports skipped/unconfigured methods separately
- Updated OpenAPI spec with new /sources/{id} route
- Version bumped to 2.3.3

Fixed endpoints: /search, /info/:id, /trending, /filter, /episodes/:id,
/sources/:id (new), /stream, /watch, /download
All endpoints return consistent { success, results/message } format
Proper 400/404/503 responses for invalid inputs and failures
2026-08-25 06:45:38 +05:30
Shinei Nouzen 160f83acf7 docs: remove unverified streaming claims — mark Worker as experimental/untitled
- README.md: streaming status section restored to original
- README.md: Worker deployment marked as experimental, untested
- CHANGELOG.md: edge-to-edge bypass marked as theoretical/untested
- Honest about what's verified vs unverified
2026-08-05 21:01:42 +05:30
Shinei Nouzen 5b020e53b0 docs: update README.md for v2.3.2 — Cloudflare Worker deployment, version badges, streaming status 2026-08-05 20:54:33 +05:30
Shinei Nouzen a2ba1df831 feat: add Cloudflare Worker implementation for edge-to-edge streaming bypass
- Add worker.js: full API on Cloudflare's edge network (1720 lines)
- Add wrangler.toml: deployment configuration for wrangler CLI
- Streaming endpoints now bypass Cloudflare bot detection via edge-to-edge
- Zero cold starts, 100K free requests/day, no npm dependencies
- 15 endpoints: search, trending, info, episodes, watch, genres, random, etc.
- Self-healing fallback: direct → ScraperAPI → FlareSolverr
- Proper code style: box headers, JSDoc, feature markers, inline notes
- Bump version to 2.3.2 across package.json, server.js, apiRoutes.js

Fixes #2
2026-08-05 20:39:31 +05:30
Shinei Nouzen d08bb86931 Update README with API status notice
Added note about API status and hosting requirements.
2026-07-25 20:02:57 +05:30
Shinei Nouzen 4912b5d6eb docs: update CONTRIBUTING.md with bypass solutions guide
- Added Contributing Bypass Solutions section (priority)
- Added free/open source tool table
- Added step-by-step guide for contributors
- Added code style examples (headers, section markers, feature markers)
2026-07-02 10:13:33 +05:30
Shinei Nouzen 5d2534eff2 docs: add bypass solutions for community contributions
Added Contributing Bypass Solutions section to README with:
- Free/open source tools (nodriver, Camoufox, curl_cffi, FlareSolverr, etc.)
- Paid APIs (Scrape.do 1K free, ZenRows, ScrapingBee)
- Implementation guide for contributors
- .env.example updated with SCRAPE_DO_TOKEN

Encourages community to implement streaming bypass methods.
2026-07-02 10:12:28 +05:30
Shinei Nouzen 45b436d752 chore: remove Cloudflare Worker edition
Worker approach tested and confirmed non-functional:
- Both AniList and Miruro block CF Worker IPs (HTTP 403)
- Edge-to-edge assumption was wrong

Removed:
- worker.js (885 lines)
- README Worker section
- docs/architecture.md Worker section
- Cloudflare Worker script deleted from CF account

CHANGELOG.md kept as historical record.
2026-07-02 10:01:13 +05:30
Shinei Nouzen 4293683ae4 fix: update docs to reflect CF Worker blocking reality
Both AniList and Miruro block Cloudflare Worker IPs (HTTP 403).
Worker approach tested and confirmed non-functional:
- /api/genres → AniList 403: 'manually blocked'
- /api/search → AniList 403: 'manually blocked'
- /api/episodes → Miruro 403: blocked

Updated README.md and docs/architecture.md:
- Worker section marked as experimental
- Troubleshooting reordered: FlareSolverr > ScraperAPI > self-host
- Architecture doc updated with actual test results
2026-07-02 09:58:21 +05:30
Shinei Nouzen 2c97ca2dd8 docs: add Cloudflare Worker edition documentation
- README.md: Added Worker deployment section, updated troubleshooting
  - Fix 1: Cloudflare Worker (best — free, works)
  - Fix 2: ScraperAPI (paid, 9/mo)
  - Fix 3: FlareSolverr (self-hosted)
  - Fix 4: Self-host API
  - Added Worker vs Vercel comparison table
- docs/architecture.md: Added Worker architecture section
  - Why Workers work (edge-to-edge bypass)
  - Worker runtime differences table
  - 15 Worker endpoints listed
  - Deploy instructions
- CHANGELOG.md: Added v2.3.1 entry for Worker edition
2026-07-02 09:34:18 +05:30
Shinei Nouzen 3f667fe87f feat: Cloudflare Worker edition — runs on edge, bypasses Cloudflare
- 15 endpoints (search, trending, info, episodes, watch, etc.)
- Pipe requests bypass Cloudflare bot detection (edge-to-edge)
- Zero cold starts, 100K free requests/day
- No npm dependencies (native fetch + DecompressionStream)
- Full codebase comments matching repo style:
  - Box-style headers (project/author/license)
  - Section markers (// ==== SECTION ====)
  - Feature markers (// ---- FEATURE: XYZ ----)
  - Function-level JSDoc (params, returns, tricks)
  - Inline notes for non-obvious logic
  - Footer with endpoint summary
2026-07-02 09:16:00 +05:30
Shinei Nouzen 015aa67ec2 fix: ScraperAPI requires premium plan for protected domains like Miruro
- pipe.js: add premium=true to ScraperAPI URL
- README.md: update ScraperAPI cost to $49/mo (premium required)
- .env.example: note protected domain requirement
- CHANGELOG.md: update ScraperAPI cost info
2026-07-02 08:58:05 +05:30
Shinei Nouzen a918ed9d1a chore: trigger redeploy for SCRAPER_API_KEY fix 2026-07-02 08:25:31 +05:30
Shinei Nouzen 125f088570 chore: trigger Vercel redeploy for new SCRAPER_API_KEY 2026-07-02 08:14:48 +05:30
15 changed files with 2412 additions and 79 deletions
+7 -1
View File
@@ -16,9 +16,10 @@ PRU_PROXY_KEY=a54d389c18527d9fd3e7f0643e27edbe
# SELF-HEALING FALLBACK METHODS (optional, enables auto-recovery) # SELF-HEALING FALLBACK METHODS (optional, enables auto-recovery)
# ══════════════════════════════════════════════════════════════ # ══════════════════════════════════════════════════════════════
# Method 2: ScraperAPI (free 1,000 req/month, no CC needed) # Method 2: ScraperAPI (requires premium plan — $49/month)
# Sign up at https://www.scraperapi.com/ # Sign up at https://www.scraperapi.com/
# Enables automatic fallback when direct requests are blocked by Cloudflare # Enables automatic fallback when direct requests are blocked by Cloudflare
# NOTE: Miruro is a protected domain — free plan will NOT work
SCRAPER_API_KEY= SCRAPER_API_KEY=
# Method 3: FlareSolverr (self-hosted browser proxy) # Method 3: FlareSolverr (self-hosted browser proxy)
@@ -26,3 +27,8 @@ SCRAPER_API_KEY=
# Deploy on Render.com free tier, Oracle Cloud, or any VPS # Deploy on Render.com free tier, Oracle Cloud, or any VPS
# Solves Cloudflare Turnstile challenges automatically # Solves Cloudflare Turnstile challenges automatically
FLARESOLVERR_URL= 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=
+141 -1
View File
@@ -1,5 +1,145 @@
# Changelog # 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
#### New: `worker.js` (Cloudflare Worker)
- Full API running on Cloudflare's edge network
- **Streaming endpoints work** — edge-to-edge requests bypass bot detection
- Zero cold starts, 100K free requests/day
- No npm dependencies (native `fetch` + `DecompressionStream`)
- 15 endpoints: search, trending, info, episodes, watch, genres, random
#### Why Workers Work
```
Vercel (datacenter) → miruro.to → Cloudflare blocks ❌
Worker (edge) → miruro.to → Cloudflare trusts itself ✅
```
#### Deploy
```bash
npm install -g wrangler
wrangler login
wrangler deploy worker.js --name miruroapi
```
#### Files Changed
- `worker.js` — Cloudflare Worker implementation (885 lines)
- `README.md` — Added Worker deployment section, updated troubleshooting
- `docs/architecture.md` — Added Worker architecture section
---
## v2.3.0 ## v2.3.0
### Self-Healing Fallback System — Auto-Recovery from Cloudflare Blocks ### Self-Healing Fallback System — Auto-Recovery from Cloudflare Blocks
@@ -25,7 +165,7 @@ GET /api/pipe-health
#### Configuration (all optional, zero-config to start) #### Configuration (all optional, zero-config to start)
| Env Var | Purpose | Free? | | Env Var | Purpose | Free? |
|---------|---------|-------| |---------|---------|-------|
| `SCRAPER_API_KEY` | ScraperAPI proxy | Yes (1K req/mo) | | `SCRAPER_API_KEY` | ScraperAPI proxy | No ($49/mo premium required for protected domains) |
| `FLARESOLVERR_URL` | FlareSolverr browser | Yes (self-hosted) | | `FLARESOLVERR_URL` | FlareSolverr browser | Yes (self-hosted) |
| `PIPE_OBF_KEY` | XOR decode key | N/A | | `PIPE_OBF_KEY` | XOR decode key | N/A |
+61 -1
View File
@@ -30,7 +30,7 @@ npm run dev
MiruroAPI/ MiruroAPI/
├── server.js # Express entry point ├── server.js # Express entry point
├── src/ ├── src/
│ ├── helpers/ # AniList, pipe, cache integrations │ ├── helpers/ # AniList, pipe + embed providers, cache integrations
│ └── routes/ # Express routes │ └── routes/ # Express routes
├── public/ # Static files (landing page, Swagger UI) ├── public/ # Static files (landing page, Swagger UI)
└── docs/ # API documentation └── docs/ # API documentation
@@ -43,6 +43,9 @@ MiruroAPI/
3. Update `docs/endpoints.md` with documentation 3. Update `docs/endpoints.md` with documentation
4. Test with `curl http://localhost:3000/api/your-endpoint` 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 ## Code Style
- Use CommonJS (`require`/`module.exports`) - Use CommonJS (`require`/`module.exports`)
@@ -50,6 +53,37 @@ MiruroAPI/
- Follow existing patterns in the codebase - Follow existing patterns in the codebase
- Keep helpers focused on one task - 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 ## Commit Messages
Use [Conventional Commits](https://www.conventionalcommits.org/): Use [Conventional Commits](https://www.conventionalcommits.org/):
@@ -75,6 +109,32 @@ Use [Conventional Commits](https://www.conventionalcommits.org/):
- Include expected vs actual behavior - Include expected vs actual behavior
- Include your Node.js version - 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 ## License
By contributing, you agree that your contributions will be licensed under the MIT License. By contributing, you agree that your contributions will be licensed under the MIT License.
+156 -47
View File
@@ -1,3 +1,6 @@
> [!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"> <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" /> <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" />
@@ -19,15 +22,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/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/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/License-MIT-22c55e?style=flat-square&logo=mit&logoColor=white" alt="License"/>
<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/Version-2.3.3-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/Endpoints-49-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"/> <img src="https://img.shields.io/badge/Providers-14-a855f7?style=flat-square&logoColor=white" alt="Providers"/>
</p> </p>
<p align="center"> <p align="center">
<b>A complete RESTful API for anime streaming data powered by AniList GraphQL and Miruro providers</b><br/> <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/> Search, browse, filter, watch — every endpoint returns fresh data with smart caching.<br/>
46 endpoints, 12 streaming providers, M3U8 URLs with subtitles, skip timestamps, and batch operations. 49 endpoints, 14 streaming providers (12 pipe + 2 direct embed), M3U8 URLs with subtitles, skip timestamps, and batch operations.
</p> </p>
<p align="center"> <p align="center">
@@ -84,13 +87,13 @@
### Why MiruroAPI? ### Why MiruroAPI?
- 🎬 **46 Endpoints** — Complete anime data coverage - 🎬 **49 Endpoints** — Complete anime data coverage
- 🔍 **Full-Text Search** — Search anime by keyword with suggestions - 🔍 **Full-Text Search** — Search anime by keyword with suggestions
- 🎭 **Characters & Voice Actors** — Full character data from AniList - 🎭 **Characters & Voice Actors** — Full character data from AniList
- 🎯 **Advanced Filtering** — Genre, year, season, format, sort - 🎯 **Advanced Filtering** — Genre, year, season, format, sort
- 🏆 **Trending & Popular** — Discover what's hot right now - 🏆 **Trending & Popular** — Discover what's hot right now
- 📅 **Airing Schedule** — See what's airing on any date - 📅 **Airing Schedule** — See what's airing on any date
- 📡 **12 Streaming Providers** — M3U8 streaming sources - 📡 **14 Streaming Providers** — M3U8 streaming sources
- ⏭️ **Skip Timestamps** — OP/ED skip data - ⏭️ **Skip Timestamps** — OP/ED skip data
- 🎭 **Character & Staff Search** — Search characters and staff by name - 🎭 **Character & Staff Search** — Search characters and staff by name
- 🎬 **Batch Episodes** — Fetch multiple episode sources in parallel - 🎬 **Batch Episodes** — Fetch multiple episode sources in parallel
@@ -98,14 +101,31 @@
- 🔒 **CORS Enabled** — Works from any frontend, no proxy needed - 🔒 **CORS Enabled** — Works from any frontend, no proxy needed
- 🚀 **Zero-Config Deploy** — One click to Vercel, or run standalone with Express - 🚀 **Zero-Config Deploy** — One click to Vercel, or run standalone with Express
### 🔄 Self-Healing System (v2.3.0) ### 🔄 Self-Healing System (v2.3.2)
When Cloudflare blocks streaming requests, the API automatically tries fallback methods: 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) - **Direct** → Mirror rotation across 4 domains (miruro.to, miruro.ru, miruro.bz, miruro.tv)
- **ScraperAPI** → Free proxy bypass (1K req/month) - **ScraperAPI** → Proxy bypass via premium residential IPs ($49/mo)
- **FlareSolverr** → Self-hosted browser proxy - **FlareSolverr** → Self-hosted browser proxy
Zero-config: works out of the box. Add `SCRAPER_API_KEY` env var for automatic Cloudflare bypass. 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 Status
@@ -113,18 +133,18 @@ Zero-config: works out of the box. Add `SCRAPER_API_KEY` env var for automatic C
> >
> 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. > 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 | | What Works | What's Blocked (pipe-based) |
|:---|:---| |:---|:---|
| Search, suggestions, filter | Episodes, sources, stream | | Search, suggestions, filter | Episodes via pipe (503) |
| Info, characters, relations | Watch, download | | Info, characters, relations | Stream/download via pipe (503) |
| Trending, popular, schedule | All pipe-dependent endpoints | | Trending, popular, schedule | Watch via pipe (503) |
| Genres, tags, calendar | Streaming provider data | | **NEW: /api/embed (direct embeds)** | |
| All AniList-backed endpoints | | | All AniList-backed endpoints | |
**To fix streaming**, set one of these environment variables in Vercel: **To fix streaming**, set one of these environment variables in Vercel:
```bash ```bash
# Option 1: ScraperAPI (free 1K requests/month) # Option 1: ScraperAPI (requires premium plan — $49/month)
SCRAPER_API_KEY=your_scraperapi_key SCRAPER_API_KEY=your_scraperapi_key
# Option 2: FlareSolverr (self-hosted, unlimited) # Option 2: FlareSolverr (self-hosted, unlimited)
@@ -143,7 +163,8 @@ flowchart TD
C -- MISS --> E{"🔍 Which Source?"} C -- MISS --> E{"🔍 Which Source?"}
E -- Metadata --> F["📡 AniList GraphQL<br/>graphql.anilist.co"] E -- Metadata --> F["📡 AniList GraphQL<br/>graphql.anilist.co"]
E -- Streaming --> G["📺 Miruro Pipe<br/>miruro.{to,ru,bz,tv}/api/secure/pipe"] 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"]
F --> H["AniList Response<br/>JSON"] F --> H["AniList Response<br/>JSON"]
G --> I["Pipe Response<br/>base64url + gzip"] G --> I["Pipe Response<br/>base64url + gzip"]
@@ -158,6 +179,7 @@ flowchart TD
style E fill:#1e1e2e,stroke:#a855f7,color:#f1f5f9 style E fill:#1e1e2e,stroke:#a855f7,color:#f1f5f9
style F fill:#1e1e2e,stroke:#06b6d4,color:#f1f5f9 style F fill:#1e1e2e,stroke:#06b6d4,color:#f1f5f9
style G fill:#1e1e2e,stroke:#eab308,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 H fill:#1e1e2e,stroke:#06b6d4,color:#f1f5f9
style I fill:#1e1e2e,stroke:#eab308,color:#f1f5f9 style I fill:#1e1e2e,stroke:#eab308,color:#f1f5f9
style J fill:#1e1e2e,stroke:#22c55e,color:#f1f5f9 style J fill:#1e1e2e,stroke:#22c55e,color:#f1f5f9
@@ -175,7 +197,7 @@ flowchart TD
- **AniList GraphQL** for rich metadata - **AniList GraphQL** for rich metadata
- **Miruro pipe** for streaming sources - **Miruro pipe** for streaming sources
- **Smart caching** with configurable TTL - **Smart caching** with configurable TTL
- **46 RESTful endpoints** - **49 RESTful endpoints**
- **Gzip compression** — 30-70% smaller responses - **Gzip compression** — 30-70% smaller responses
- **Request logging** — method, path, status, duration - **Request logging** — method, path, status, duration
- **Graceful error handling** per endpoint - **Graceful error handling** per endpoint
@@ -208,6 +230,7 @@ flowchart TD
- **Sub/Dub** support per provider - **Sub/Dub** support per provider
- **Codec and fansub** metadata - **Codec and fansub** metadata
- **Batch episode fetching** — multiple sources in parallel - **Batch episode fetching** — multiple sources in parallel
- **Direct embed providers** (megavid, anixo) — bypass Cloudflare
- **Quality fallback** — auto 1080p → 720p → 360p - **Quality fallback** — auto 1080p → 720p → 360p
- **Subtitle extraction** — expose subtitle URLs - **Subtitle extraction** — expose subtitle URLs
@@ -230,14 +253,14 @@ flowchart TD
| Feature | Description | Status | | Feature | Description | Status |
|:---|:---|:---:| |:---|:---|:---:|
| 🎬 46 API Endpoints | Complete anime data coverage | ✅ | | 🎬 49 API Endpoints | Complete anime data coverage | ✅ |
| 🔍 Full-Text Search | Keyword search with pagination | ✅ | | 🔍 Full-Text Search | Keyword search with pagination | ✅ |
| 💡 Search Suggestions | Fast autocomplete | ✅ | | 💡 Search Suggestions | Fast autocomplete | ✅ |
| 🎯 Advanced Filtering | Genre, year, season, format, sort | ✅ | | 🎯 Advanced Filtering | Genre, year, season, format, sort | ✅ |
| 🎭 Characters + Voice Actors | Full character data from AniList | ✅ | | 🎭 Characters + Voice Actors | Full character data from AniList | ✅ |
| 🔗 Relations & Recommendations | Related anime discovery | ✅ | | 🔗 Relations & Recommendations | Related anime discovery | ✅ |
| ⏭️ Skip Timestamps | OP/ED skip data | ✅ | | ⏭️ Skip Timestamps | OP/ED skip data | ✅ |
| 📡 12 Streaming Providers | M3U8 streaming sources | ✅ | | 📡 14 Streaming Providers | M3U8 streaming sources | ✅ |
| 🔄 Smart Caching | In-memory Map with TTL | ✅ | | 🔄 Smart Caching | In-memory Map with TTL | ✅ |
| 🚀 One-Click Deploy | Vercel button deployment | ✅ | | 🚀 One-Click Deploy | Vercel button deployment | ✅ |
| 🏗️ Express Mode | Standalone server with `npm start` | ✅ | | 🏗️ Express Mode | Standalone server with `npm start` | ✅ |
@@ -262,6 +285,13 @@ flowchart TD
| 📺 **Miruro** | `miruro.bz` | Mirror domain | | 📺 **Miruro** | `miruro.bz` | Mirror domain |
| 📺 **Miruro** | `miruro.tv` | 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 ### 🎬 Streaming Providers
| Provider | Provider | Provider | Provider | | Provider | Provider | Provider | Provider |
@@ -362,7 +392,7 @@ MiruroAPI/
│ │ └── 📄 cache.js # 💾 In-memory cache with TTL │ │ └── 📄 cache.js # 💾 In-memory cache with TTL
│ │ │ │
│ └── 📂 routes/ # 🛤️ Express routes │ └── 📂 routes/ # 🛤️ Express routes
│ └── 📄 apiRoutes.js # 🌐 Main API routes (46 endpoints) │ └── 📄 apiRoutes.js # 🌐 Main API routes (49 endpoints)
│ │
├── 📄 server.js # 🚀 Express server entry point ├── 📄 server.js # 🚀 Express server entry point
├── 📄 package.json # 📦 Dependencies & scripts ├── 📄 package.json # 📦 Dependencies & scripts
@@ -436,12 +466,12 @@ bun dev
| `ALLOWED_ORIGINS` | `*` | Comma-separated allowed origins (restricts CORS in production) | | `ALLOWED_ORIGINS` | `*` | Comma-separated allowed origins (restricts CORS in production) |
| `PIPE_OBF_KEY` | `71951034...` | Pipe response XOR obfuscation key (hex, 32 chars) | | `PIPE_OBF_KEY` | `71951034...` | Pipe response XOR obfuscation key (hex, 32 chars) |
| `PRU_PROXY_KEY` | `a54d389c...` | PRU proxy URL XOR key (hex, 32 chars) | | `PRU_PROXY_KEY` | `a54d389c...` | PRU proxy URL XOR key (hex, 32 chars) |
| `SCRAPER_API_KEY` | — | ScraperAPI key for Cloudflare bypass (free 1K req/month) | | `SCRAPER_API_KEY` | — | ScraperAPI key for Cloudflare bypass (requires premium plan, $49/mo) |
| `FLARESOLVERR_URL` | — | FlareSolverr URL for browser-based bypass (e.g., `http://127.0.0.1:8191`) | | `FLARESOLVERR_URL` | — | FlareSolverr URL for browser-based bypass (e.g., `http://127.0.0.1:8191`) |
> ⚠️ **Required for production:** Set `ALLOWED_ORIGINS` to restrict CORS. Set `PIPE_OBF_KEY` and `PRU_PROXY_KEY` for pipe decoding. > ⚠️ **Required for production:** Set `ALLOWED_ORIGINS` to restrict CORS. Set `PIPE_OBF_KEY` and `PRU_PROXY_KEY` for pipe decoding.
> >
> 📺 **For streaming:** Set `SCRAPER_API_KEY` or `FLARESOLVERR_URL` to bypass Cloudflare bot protection. See [How to Fix Streaming Issues](#-how-to-fix-streaming-issues). > 📺 **For streaming:** Set `SCRAPER_API_KEY` (requires ScraperAPI premium plan) or `FLARESOLVERR_URL` (free, self-hosted) to bypass Cloudflare bot protection. See [How to Fix Streaming Issues](#-how-to-fix-streaming-issues).
### Vercel Configuration ### Vercel Configuration
@@ -533,14 +563,15 @@ console.log(resp.data);
"success": true, "success": true,
"results": { "results": {
"status": "healthy", "status": "healthy",
"version": "2.3.0", "version": "2.3.3",
"uptime": "0h 0m 34s", "uptime": "0h 0m 34s",
"uptimeSeconds": 34, "uptimeSeconds": 34,
"timestamp": "2026-06-09T09:55:00.884Z", "timestamp": "2026-06-09T09:55:00.884Z",
"node": "v24.14.1", "node": "v24.14.1",
"memory": { "used": "13MB", "total": "15MB" }, "memory": { "used": "13MB", "total": "15MB" },
"endpoints": 46, "endpoints": 49,
"providers": ["kiwi","pewe","bee","bonk","bun","ally","nun","twin","cog","moo","hop","telli"] "providers": ["kiwi","pewe","bee","bonk","bun","ally","nun","twin","cog","moo","hop","telli"],
"embedProviders": ["megavid", "anixo"]
} }
} }
``` ```
@@ -580,7 +611,7 @@ console.log(resp.data);
"uptime": "0h 0m 34s", "uptime": "0h 0m 34s",
"requests": { "total": 156, "errors": 3, "successRate": "98.1%" }, "requests": { "total": 156, "errors": 3, "successRate": "98.1%" },
"cache": { "size": 12, "maxSize": 100, "ttl": "1 min" }, "cache": { "size": 12, "maxSize": 100, "ttl": "1 min" },
"endpoints": 46, "endpoints": 49,
"timestamp": "2026-06-09T09:55:00.884Z" "timestamp": "2026-06-09T09:55:00.884Z"
} }
} }
@@ -1414,6 +1445,32 @@ docker build -t miruroapi .
docker run -p 3000:3000 miruroapi docker run -p 3000:3000 miruroapi
``` ```
### ⚡ Cloudflare Worker (Experimental)
> **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**.
```bash
# Install wrangler CLI
npm install -g wrangler
# Login to Cloudflare
wrangler login
# Deploy worker
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.
**Features:** Zero cold starts, 100K free requests/day, no npm dependencies, 15 API endpoints.
**Optional env vars:**
```bash
wrangler secret put SCRAPER_API_KEY # ScraperAPI premium key ($49/mo)
wrangler secret put FLARESOLVERR_URL # FlareSolverr Docker URL
```
--- ---
## 📜 Available Scripts ## 📜 Available Scripts
@@ -1451,7 +1508,8 @@ docker run -p 3000:3000 miruroapi
| Version | Date | Key Changes | | Version | Date | Key Changes |
|:---|:---|:---| |:---|:---|:---|
| **2.3.0** | 2026-07-02 | Self-healing fallback system — multi-method pipe recovery, /api/pipe-health endpoint, ScraperAPI + FlareSolverr support | | **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.1.4** | 2026-06-25 | Security hardening — XOR keys to env vars, CORS restriction, input sanitization, bug fixes (multi-search, deepTranslate, getBestStream) | | **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.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 | | **2.1.2** | 2026-06-25 | Streaming architecture — pru proxy decode, CDN CORS proxy, raw stream URLs |
@@ -1492,22 +1550,9 @@ Cloudflare detects:
- Missing browser fingerprint headers - Missing browser fingerprint headers
- Non-residential traffic patterns - Non-residential traffic patterns
#### Fix 1: ScraperAPI (Easiest — Free Tier) #### Fix 1: FlareSolverr (Best — Free, Works!)
ScraperAPI proxies your requests through residential IPs, bypassing Cloudflare. FlareSolverr runs a headless browser that bypasses Cloudflare automatically. This is the most reliable fix.
```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:** Free for 1K requests/month. Paid plans from $49/month for 250K requests.
#### Fix 2: FlareSolverr (Self-Hosted — Unlimited)
FlareSolverr runs a headless browser that bypasses Cloudflare automatically.
```bash ```bash
# 1. Run FlareSolverr on your own server # 1. Run FlareSolverr on your own server
@@ -1524,9 +1569,22 @@ FLARESOLVERR_URL=http://your-server-ip:8191
- Public IP address - Public IP address
- Docker installed - Docker installed
#### Fix 2: ScraperAPI (Paid — $49/month)
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 #### Fix 3: Self-Host the Entire API
If you run the API on your own VPS (not Vercel), requests come from your residential/datacenter IP which may not be blocked. If you run the API on your own VPS (not Vercel), requests come from your IP which may not be blocked.
```bash ```bash
# Clone and install # Clone and install
@@ -1565,6 +1623,49 @@ 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 ### 🐛 Debug Mode
```bash ```bash
@@ -1620,13 +1721,19 @@ Yes! Use <code>npm start</code> to run the Express server on any VPS, Docker con
<details> <details>
<summary><b>🎬 Which streaming providers are available?</b></summary> <summary><b>🎬 Which streaming providers are available?</b></summary>
<br/> <br/>
12 providers: kiwi, pewe, bee, bonk, bun, ally, nun, twin, cog, moo, hop, telli. Not all anime are available on every provider. 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.
</details> </details>
<details> <details>
<summary><b>❌ Why are streaming endpoints returning 500 errors?</b></summary> <summary><b>❌ Why are streaming endpoints returning 500 errors?</b></summary>
<br/> <br/>
Cloudflare's bot protection blocks requests from Vercel's datacenter IPs. The pipe endpoint (<code>miruro.to/api/secure/pipe</code>) returns HTTP 403. To fix this, set <code>SCRAPER_API_KEY</code> (free 1K req/month from ScraperAPI) or <code>FLARESOLVERR_URL</code> (self-hosted browser proxy) in your Vercel environment variables. See <a href="#-how-to-fix-streaming-issues">How to Fix Streaming Issues</a> above. Cloudflare's bot protection blocks requests from Vercel's datacenter IPs. The pipe endpoint (<code>miruro.to/api/secure/pipe</code>) returns HTTP 403. To fix this, set <code>SCRAPER_API_KEY</code> (requires ScraperAPI premium plan, $49/mo) or <code>FLARESOLVERR_URL</code> (self-hosted, free) in your Vercel environment variables. See <a href="#-how-to-fix-streaming-issues">How to Fix Streaming Issues</a> above.
</details> </details>
<details> <details>
@@ -1658,14 +1765,14 @@ Use <code>/api/pipe-health</code> to check which pipe methods are working. It sh
### ✅ Completed ### ✅ Completed
- [x] 🎬 46 API endpoints covering all data - [x] 🎬 49 API endpoints covering all data
- [x] 🔍 Full-text search with pagination - [x] 🔍 Full-text search with pagination
- [x] 💡 Search suggestions for autocomplete - [x] 💡 Search suggestions for autocomplete
- [x] 🎯 Advanced filtering (genre, year, season, format, sort) - [x] 🎯 Advanced filtering (genre, year, season, format, sort)
- [x] 🎭 Characters + voice actors from AniList - [x] 🎭 Characters + voice actors from AniList
- [x] 🔗 Relations and recommendations - [x] 🔗 Relations and recommendations
- [x] ⏭️ Skip timestamps (OP/ED) - [x] ⏭️ Skip timestamps (OP/ED)
- [x] 📡 12 streaming providers with M3U8 URLs - [x] 📡 14 streaming providers with M3U8 URLs
- [x] 🔄 Smart caching with configurable TTL - [x] 🔄 Smart caching with configurable TTL
- [x] 🚀 One-click Vercel deployment - [x] 🚀 One-click Vercel deployment
- [x] 🐳 Docker support - [x] 🐳 Docker support
@@ -1673,6 +1780,8 @@ Use <code>/api/pipe-health</code> to check which pipe methods are working. It sh
- [x] 📖 Comprehensive documentation with real API data - [x] 📖 Comprehensive documentation with real API data
- [x] 🔒 Security hardening (XOR keys in env vars, CORS restriction, input sanitization) - [x] 🔒 Security hardening (XOR keys in env vars, CORS restriction, input sanitization)
- [x] 🛡️ Security headers at CDN level - [x] 🛡️ Security headers at CDN level
- [x] 🎬 49 API endpoints with direct embed providers
- [x] 🎬 Direct embed providers (megavid, anixo) bypass Cloudflare
--- ---
+2
View File
@@ -198,3 +198,5 @@ Error responses:
| axios | ^1.8.0 | HTTP requests | | axios | ^1.8.0 | HTTP requests |
| cors | ^2.8.5 | CORS headers | | cors | ^2.8.5 | CORS headers |
| dotenv | ^16.4.0 | Environment variables | | dotenv | ^16.4.0 | Environment variables |
+50 -1
View File
@@ -1,6 +1,6 @@
# API Endpoints Reference # API Endpoints Reference
Complete documentation for all 45 MiruroAPI v2.3.0 endpoints. Complete documentation for all 49 MiruroAPI v2.3.3 endpoints.
--- ---
@@ -57,6 +57,11 @@ Complete documentation for all 45 MiruroAPI v2.3.0 endpoints.
- [Download URL](#download-url) - [Download URL](#download-url)
- [Watch (Streaming)](#watch-streaming) - [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 ### Utility
- [Compare](#compare) - [Compare](#compare)
- [Genre Stats](#genre-stats) - [Genre Stats](#genre-stats)
@@ -576,6 +581,50 @@ 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 ## Health Check
``` ```
+29 -3
View File
@@ -5,13 +5,39 @@ This guide explains how to get streaming URLs from MiruroAPI in 3 steps.
## Overview ## Overview
``` ```
Step 1: Get Episodes → /api/episodes/:anilistId Option A (Pipe-based — may be Cloudflare-blocked):
Step 2: Get Stream URLs → /api/watch/:provider/:anilistId/:category/:slug Step 1: Get Episodes → /api/episodes/:anilistId
Step 3: Play M3U8 → Use HLS player 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>
``` ```
--- ---
## 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 ## Step 1: Get Episodes
Each anime has episodes from multiple providers. Fetch the episode list with the AniList ID. Each anime has episodes from multiple providers. Fetch the episode list with the AniList ID.
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "miruro-api", "name": "miruro-api",
"version": "1.4.0", "version": "2.3.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "miruro-api", "name": "miruro-api",
"version": "1.4.0", "version": "2.3.2",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"axios": "^1.8.0", "axios": "^1.8.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "miruro-api", "name": "miruro-api",
"version": "2.3.0", "version": "2.3.3",
"description": "RESTful API for Miruro - Anime streaming data powered by AniList GraphQL and Miruro streaming providers", "description": "RESTful API for Miruro - Anime streaming data powered by AniList GraphQL and Miruro streaming providers",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
+4 -2
View File
@@ -101,10 +101,12 @@ app.use((req, res, next) => {
return res.status(403).json({ success: false, message: "Origin not allowed" }); return res.status(403).json({ success: false, message: "Origin not allowed" });
} }
} else { } else {
// Default: allow all origins for public API usage by anime frontends
res.setHeader("Access-Control-Allow-Origin", origin || "*"); res.setHeader("Access-Control-Allow-Origin", origin || "*");
} }
res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS"); res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-No-Compression"); 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-Max-Age", "86400"); res.setHeader("Access-Control-Max-Age", "86400");
if (req.method === "OPTIONS") { if (req.method === "OPTIONS") {
@@ -301,7 +303,7 @@ app.use((req, res) => {
if (!process.env.VERCEL) { if (!process.env.VERCEL) {
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`[${new Date().toISOString()}] MiruroAPI v2.3.0 listening at ${PORT}`); console.log(`[${new Date().toISOString()}] MiruroAPI v2.3.3 listening at ${PORT}`);
}); });
} }
+66 -6
View File
@@ -113,7 +113,7 @@ const methodDirect = async (encodedReq) => {
try { try {
const res = await axios.get(`${origin}${PIPE_PATH}?e=${encodedReq}`, { const res = await axios.get(`${origin}${PIPE_PATH}?e=${encodedReq}`, {
headers: HEADERS, headers: HEADERS,
timeout: 20000, timeout: 15000,
maxRedirects: 5, maxRedirects: 5,
}); });
@@ -126,6 +126,10 @@ const methodDirect = async (encodedReq) => {
lastError = e; lastError = e;
const status = e.response?.status; 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 ( if (
status && status &&
status >= 400 && status >= 400 &&
@@ -136,13 +140,17 @@ const methodDirect = async (encodedReq) => {
throw new Error(`Pipe request failed with status ${status}`); 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) { if (attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000; const delay = Math.pow(2, attempt) * 1000;
await new Promise((r) => setTimeout(r, delay)); await new Promise((r) => setTimeout(r, delay));
} }
} }
} }
throw lastError || new Error("Direct request failed after all retries"); throw lastError || new Error("Direct request failed after all retries. The streaming provider may be temporarily unavailable.");
}; };
// ══════════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════════
@@ -153,7 +161,7 @@ const methodScraperAPI = async (encodedReq) => {
if (!SCRAPER_API_KEY) throw new Error("SCRAPER_API_KEY not configured"); if (!SCRAPER_API_KEY) throw new Error("SCRAPER_API_KEY not configured");
const targetUrl = `${MIRURO_ORIGINS[0]}${PIPE_PATH}?e=${encodedReq}`; const targetUrl = `${MIRURO_ORIGINS[0]}${PIPE_PATH}?e=${encodedReq}`;
const scraperUrl = `https://api.scraperapi.com/?api_key=${SCRAPER_API_KEY}&url=${encodeURIComponent(targetUrl)}`; const scraperUrl = `https://api.scraperapi.com/?api_key=${SCRAPER_API_KEY}&premium=true&url=${encodeURIComponent(targetUrl)}`;
const res = await axios.get(scraperUrl, { const res = await axios.get(scraperUrl, {
headers: { headers: {
@@ -220,10 +228,13 @@ const pipeRequest = async (path, query) => {
const encodedReq = encodePipeRequest(payload); const encodedReq = encodePipeRequest(payload);
const errors = []; const errors = [];
const availableMethods = METHODS.filter((m) => !m.requires || m.requires());
for (const method of METHODS) { if (availableMethods.length === 0) {
if (method.requires && !method.requires()) continue; 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 availableMethods) {
try { try {
const result = await method.fn(encodedReq); const result = await method.fn(encodedReq);
return result.data; return result.data;
@@ -232,8 +243,14 @@ 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( throw new Error(
`All pipe methods failed: ${errors.map((e) => `${e.method}(${e.error})`).join(", ")}` `${baseMessage}: ${errors.map((e) => `${e.method}(${e.error})`).join(", ")}. ` +
"Configure SCRAPER_API_KEY or FLARESOLVERR_URL for additional fallback methods."
); );
}; };
@@ -575,6 +592,46 @@ 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 = { module.exports = {
getEpisodes, getEpisodes,
getSources, getSources,
@@ -590,6 +647,9 @@ module.exports = {
deepTranslate, deepTranslate,
injectSourceSlugs, injectSourceSlugs,
pipeHealthCheck, pipeHealthCheck,
EMBED_PROVIDERS,
getEmbedUrl,
getEmbedUrls,
}; };
// ══════════════════════════════════════════════════════════════ END: pipe.js // ══════════════════════════════════════════════════════════════ END: pipe.js
+142 -13
View File
@@ -678,7 +678,12 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
jsonResponse(res, result); jsonResponse(res, result);
} catch (err) { } catch (err) {
jsonError(res, err.message); 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);
} }
}); });
@@ -699,7 +704,7 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
} }
}); });
// ---- FEATURE: Streaming sources (detailed/manual endpoint) ---- // ---- FEATURE: Streaming sources (query-based endpoint) ----
router.get("/sources", async (req, res) => { router.get("/sources", async (req, res) => {
try { try {
const { episodeId, provider, anilistId, category = "sub" } = req.query; const { episodeId, provider, anilistId, category = "sub" } = req.query;
@@ -710,7 +715,27 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
const result = await pipe.getSources(episodeId, provider, parseInt(anilistId), category); const result = await pipe.getSources(episodeId, provider, parseInt(anilistId), category);
jsonResponse(res, result); jsonResponse(res, result);
} catch (err) { } catch (err) {
jsonError(res, err.message); 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);
} }
}); });
@@ -735,7 +760,12 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
skipTimes, skipTimes,
}); });
} catch (err) { } catch (err) {
jsonError(res, err.message); 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);
} }
}); });
@@ -750,7 +780,12 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
const result = await pipe.getDownloadUrl(provider, parseInt(anilistId), category, slug); const result = await pipe.getDownloadUrl(provider, parseInt(anilistId), category, slug);
jsonResponse(res, result); jsonResponse(res, result);
} catch (err) { } catch (err) {
jsonError(res, err.message); 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);
} }
}); });
@@ -768,10 +803,94 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
skipTimes, skipTimes,
}); });
} catch (err) { } catch (err) {
jsonError(res, err.message); 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);
} }
}); });
// ══════════════════════════════════════════════════════════════
// 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 // UTILITY FEATURES
// ══════════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════════
@@ -1124,7 +1243,7 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
success: true, success: true,
results: { results: {
status: "healthy", status: "healthy",
version: "2.3.0", version: "2.3.3",
uptime: `${hours}h ${minutes}m ${seconds}s`, uptime: `${hours}h ${minutes}m ${seconds}s`,
uptimeSeconds: uptime, uptimeSeconds: uptime,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
@@ -1138,8 +1257,9 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
size: require("../helpers/cache").getCacheSize(), size: require("../helpers/cache").getCacheSize(),
maxSize: 100, maxSize: 100,
}, },
endpoints: 46, endpoints: 49,
providers: ["kiwi", "pewe", "bee", "bonk", "bun", "ally", "nun", "twin", "cog", "moo", "hop", "telli"], providers: ["kiwi", "pewe", "bee", "bonk", "bun", "ally", "nun", "twin", "cog", "moo", "hop", "telli"],
embedProviders: ["megavid", "anixo"],
}, },
}); });
}); });
@@ -1154,6 +1274,9 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
const failed = Object.entries(results).filter( const failed = Object.entries(results).filter(
([, v]) => v.status === "failed" ([, v]) => v.status === "failed"
); );
const skipped = Object.entries(results).filter(
([, v]) => v.status === "skipped"
);
res.json({ res.json({
success: true, success: true,
@@ -1161,10 +1284,11 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
methods: results, methods: results,
working: working.length, working: working.length,
failed: failed.length, failed: failed.length,
skipped: skipped.length,
total: Object.keys(results).length, total: Object.keys(results).length,
recommendation: recommendation:
working.length === 0 working.length === 0
? "All methods blocked. Check Cloudflare status or add SCRAPER_API_KEY / FLARESOLVERR_URL." ? "All methods blocked or unconfigured. Set SCRAPER_API_KEY or FLARESOLVERR_URL in .env to enable fallback methods. See .env.example for details."
: working[0] : working[0]
? `Preferred: ${working[0][0]} (${working[0][1].latency})` ? `Preferred: ${working[0][0]} (${working[0][1].latency})`
: "No methods available", : "No methods available",
@@ -1172,7 +1296,7 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
}, },
}); });
} catch (err) { } catch (err) {
jsonError(res, err.message); jsonError(res, err.message, 500);
} }
}); });
@@ -1198,7 +1322,8 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
currentSize: require("../helpers/cache").getCacheSize(), currentSize: require("../helpers/cache").getCacheSize(),
description: "Map-based cache with TTL expiration and FIFO eviction", description: "Map-based cache with TTL expiration and FIFO eviction",
}, },
endpoints: 46, endpoints: 49,
embedProviders: 2,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}, },
}); });
@@ -1211,7 +1336,7 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
info: { info: {
title: "MiruroAPI", title: "MiruroAPI",
description: "Free REST API for anime streaming data — AniList GraphQL + Miruro streaming providers", description: "Free REST API for anime streaming data — AniList GraphQL + Miruro streaming providers",
version: "2.3.0", version: "2.3.3",
contact: { name: "Shineii86", url: "https://github.com/Shineii86/MiruroAPI" }, contact: { name: "Shineii86", url: "https://github.com/Shineii86/MiruroAPI" },
}, },
servers: [ servers: [
@@ -1250,7 +1375,8 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
"/anime/{id}/recommendations": { get: { summary: "Recommendations", tags: ["Anime Details"] } }, "/anime/{id}/recommendations": { get: { summary: "Recommendations", tags: ["Anime Details"] } },
"/episodes/{id}": { get: { summary: "Episode list", tags: ["Streaming & Downloading"] } }, "/episodes/{id}": { get: { summary: "Episode list", tags: ["Streaming & Downloading"] } },
"/episodes/{id}/batch": { get: { summary: "Batch episode sources", tags: ["Streaming & Downloading"] } }, "/episodes/{id}/batch": { get: { summary: "Batch episode sources", tags: ["Streaming & Downloading"] } },
"/sources": { get: { summary: "Streaming sources (detailed)", 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"] } },
"/stream": { get: { summary: "Stream with quality fallback", tags: ["Streaming & Downloading"] } }, "/stream": { get: { summary: "Stream with quality fallback", tags: ["Streaming & Downloading"] } },
"/download": { get: { summary: "Download URL", tags: ["Streaming & Downloading"] } }, "/download": { get: { summary: "Download URL", tags: ["Streaming & Downloading"] } },
"/watch/{provider}/{anilistId}/{category}/{slug}": { get: { summary: "Streaming sources (simple)", tags: ["Streaming & Downloading"] } }, "/watch/{provider}/{anilistId}/{category}/{slug}": { get: { summary: "Streaming sources (simple)", tags: ["Streaming & Downloading"] } },
@@ -1259,6 +1385,9 @@ const createApiRoutes = (jsonResponse, jsonError, startTime) => {
"/calendar": { get: { summary: "Monthly calendar", tags: ["Utility"] } }, "/calendar": { get: { summary: "Monthly calendar", tags: ["Utility"] } },
"/timeline/{id}": { get: { summary: "Anime timeline", tags: ["Utility"] } }, "/timeline/{id}": { get: { summary: "Anime timeline", tags: ["Utility"] } },
"/providers": { get: { summary: "Provider capabilities", tags: ["System"] } }, "/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"] } }, "/health": { get: { summary: "Health check", tags: ["System"] } },
"/stats": { get: { summary: "API statistics", tags: ["System"] } }, "/stats": { get: { summary: "API statistics", tags: ["System"] } },
"/openapi": { get: { summary: "OpenAPI spec", tags: ["System"] } }, "/openapi": { get: { summary: "OpenAPI spec", tags: ["System"] } },
+9 -1
View File
@@ -82,6 +82,8 @@ const tests = [
// ---- FEATURE: Streaming endpoints ---- // ---- FEATURE: Streaming endpoints ----
{ name: "Episodes", url: "/episodes/20" }, { name: "Episodes", url: "/episodes/20" },
{ name: "Batch Episodes", url: "/episodes/20/batch?provider=kiwi&category=sub&slugs=animepahe-1,animepahe-2" }, { 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: "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: "Download", url: "/download?provider=kiwi&anilistId=20&category=sub&slug=animepahe-1" },
{ name: "Watch", url: "/watch/kiwi/20/sub/animepahe-1" }, { name: "Watch", url: "/watch/kiwi/20/sub/animepahe-1" },
@@ -91,6 +93,12 @@ const tests = [
{ name: "Genre Stats", url: "/stats/genre" }, { name: "Genre Stats", url: "/stats/genre" },
{ name: "Calendar", url: "/calendar" }, { name: "Calendar", url: "/calendar" },
{ name: "Timeline", url: "/timeline/20" }, { 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" },
]; ];
// ══════════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════════
@@ -152,7 +160,7 @@ async function runTest(test) {
* API_URL=http://localhost:3000/api node test.js # Run against local * API_URL=http://localhost:3000/api node test.js # Run against local
*/ */
async function runAll() { async function runAll() {
console.log(`\n🧪 MiruroAPI v2.3.0 — Running ${tests.length} tests...\n`); console.log(`\n🧪 MiruroAPI v2.3.3 — Running ${tests.length} tests...\n`);
// NOTE: Sequential execution — parallel requests would hit rate limiter // NOTE: Sequential execution — parallel requests would hit rate limiter
for (const test of tests) { for (const test of tests) {
+1720
View File
File diff suppressed because it is too large Load Diff
+22
View File
@@ -0,0 +1,22 @@
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"]