Files
MiruroAPI/docs/architecture.md
T
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

8.4 KiB

Architecture

Project Structure

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)
├── .dockerignore                      # Docker ignore file
│
├── public/                            # Static files served from process.cwd()
│   ├── index.html                     # Premium landing page (SVG icons, live console)
│   ├── docs.html                      # Swagger UI interactive documentation
│   ├── openapi.json                   # OpenAPI 3.0 spec
│   ├── 404.html                       # Glitch animation error page
│   ├── manifest.json                  # PWA manifest (theme: #A855F7)
│   ├── robots.txt                     # Crawler directives
│   ├── sitemap.xml                    # 4 pages (/, /tos, /privacy, /api)
│   ├── icon-dark.svg                  # Miruro dark mode SVG icon
│   ├── icon-light.svg                 # Miruro light mode SVG icon
│   ├── icon-512x512.png              # Miruro app icon
│   ├── favicon.ico                    # Classic favicon
│   ├── apple-touch-icon-180x180.png  # iOS home screen icon
│   ├── og-image.png                   # OG/Twitter share image
│   ├── privacy.html                   # Privacy policy (served at /privacy)
│   └── tos.html                       # Terms of service (served at /tos)
│
├── assets/                            # Scraped Miruro assets
│   ├── favicons/                      # All favicon variants
│   ├── logos/                         # Status page logo
│   ├── fonts/                         # Inter + FontAwesome
│   └── media/                         # Testimonial avatars
│
├── docs/                              # API documentation
│   ├── index.md                       # Overview, quick start, features
│   ├── endpoints.md                   # Full API reference (18 endpoints)
│   ├── streaming.md                   # Streaming flow guide (3-step)
│   ├── examples.md                    # cURL, JavaScript, Python
│   └── architecture.md               # This file
│
├── src/
│   ├── helpers/
│   │   ├── anilist.js                 # AniList GraphQL integration
│   │   ├── pipe.js                    # Miruro pipe integration
│   │   └── cache.js                   # In-memory Map cache with TTL
│   │
│   └── routes/
│       └── apiRoutes.js               # All route definitions
│
├── test.js                            # Integration test suite
├── CHANGELOG.md                       # Version history
├── LICENSE                            # MIT License
└── README.md                          # Full API documentation

Tech Stack

Component Technology
Runtime Node.js
Framework Express.js
Metadata AniList GraphQL (graphql.anilist.co)
Streaming Miruro pipe (miruro.{to,ru,bz,tv}/api/secure/pipe)
Deployment Vercel (Serverless)
Caching In-memory Map with 1-5 min TTL
Static Files Express static middleware

Request Flow

Client Request
    ↓
Vercel Routes (/api/* → server.js)
    ↓
Express Router (apiRoutes.js)
    ↓
Helper (e.g., anilist.js or pipe.js)
    ↓
HTTP Request to AniList GraphQL / Miruro pipe
    ↓
Response decoded (base64url + gzip for pipe)
    ↓
Returns structured JSON
    ↓
Client Response

Streaming Flow

/api/episodes/:anilistId
    ↓
    Returns: providers with sub/dub episode lists
    ↓
/api/watch/:provider/:anilistId/:category/:slug
    ↓
    Returns: streams[] with M3U8 URLs, quality, codec, fansub
    ↓
Play M3U8 in HLS player (hls.js, video.js, native)

Caching Strategy

  • Type: In-memory Map
  • TTL: 1-5 minutes depending on endpoint
  • Max Size: 100 entries with FIFO eviction
  • Key: Full request URL
  • Behavior: First request fetches from source, subsequent requests served from cache
  • Eviction: Automatic when TTL expires or max size reached

Data Sources

AniList GraphQL

  • Endpoint: https://graphql.anilist.co
  • Data: Search, suggestions, info, characters, relations, recommendations, filter, schedule
  • Format: Standard GraphQL queries

Miruro Pipe

  • Endpoint: https://miruro.{to,ru}/api/secure/pipe
  • Data: Episodes, streaming sources (M3U8 URLs)
  • Encoding: Base64url + gzip compressed request/response
  • Providers: kiwi, pewe, bee, bonk, bun, ally, nun, twin, cog, moo, hop, telli

Self-Healing Fallback System (v2.3.0)

When Cloudflare blocks streaming requests, the API automatically tries fallback methods in order:

  1. Direct → Mirror rotation across 4 domains (miruro.to, miruro.ru, miruro.bz, miruro.tv)
  2. ScraperAPI → Free proxy bypass via ScraperAPI (1K req/month, requires SCRAPER_API_KEY)
  3. FlareSolverr → Self-hosted browser proxy for full Cloudflare bypass

The system tracks success/failure rates per method and auto-rotates to the most reliable option. Check status via /api/pipe-health.

Vercel Configuration

{
  "version": 2,
  "builds": [
    {
      "src": "server.js",
      "use": "@vercel/node"
    }
  ],
  "routes": [
    {
      "src": "/api/(.*)",
      "dest": "server.js"
    },
    {
      "src": "/(.*)",
      "dest": "server.js"
    }
  ]
}
  • /api/* routes to Express for API handling
  • /* routes to Express for static file serving
  • Static files served from process.cwd() (Vercel serverless compatible)

Environment Variables

Variable Default Description
PORT 3000 Server port (Express mode only)
ALLOWED_ORIGINS * Comma-separated allowed origins

Response Format

All API responses follow this structure:

{
  "success": true,
  "results": { ... }
}

Error responses:

{
  "success": false,
  "message": "Error description"
}

Dependencies

Package Version Purpose
express ^4.21.0 Web framework
axios ^1.8.0 HTTP requests
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. ⚠️ Experimental — testing shows that both AniList and Miruro block CF Worker IPs (HTTP 403).

Why Workers Don't Work (Current)

Vercel (datacenter IP) → miruro.to → Cloudflare WAF → 403 Blocked
Cloudflare Worker (edge) → miruro.to → Cloudflare bot detection → 403 Blocked
Cloudflare Worker (edge) → graphql.anilist.co → AniList blocks worker IPs → 403 Blocked

Cloudflare Worker IPs are recognized as automated traffic and blocked by both services.

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 (Experimental)

npm install -g wrangler
wrangler login
wrangler deploy worker.js --name miruroapi