mirror of
https://github.com/Shineii86/MiruroAPI.git
synced 2026-10-22 18:25:11 +00:00
v1.3.0: Full landing page upgrade + feature parity with AniKotoAPI
- Full index.html rewrite with AniKotoAPI-style features: * Canvas particle effects (cyan particles) * Scroll-reveal animations (IntersectionObserver) * Animated stat counters (count up on scroll) * Infinite scrolling marquee (8 feature badges) * Live interactive API console (5 presets: Search, Trending, Episodes, Info, Health) * Sidebar endpoint explorer (16 endpoints, multi-language tabs: cURL/JS/Python) * 4 interactive playground cards (Search, Info, Episodes, Trending) * Custom scrollbar, JetBrains Mono font * 4-column grid footer with API/Resources/Legal * Fixed header with scroll-aware glass blur * Ambient orb gradient animations * CTA section with gradient overlay - Added public/404.html (glitch animation error page) - Added public/manifest.json (PWA, theme: #38bdf8) - Added public/robots.txt (Allow /, Disallow /api) - Added public/sitemap.xml (4 URLs) - Added public/tos.html (13 sections) - Added public/privacy.html (8 sections) - Added .env.example, CONTRIBUTING.md, LICENSE, render.yaml, test.js - Added docs/ (5 files: index, endpoints, streaming, examples, architecture) - Updated server.js: clean URL routes (/tos, /privacy), 404.html fallback - Updated package.json: devDependencies nodemon, repository, dev script - All branding: AniKotoAPI → MiruroAPI, cyan color scheme (#38bdf8)
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
# Architecture
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
MiruroAPI/
|
||||
├── server.js # Express entry point, port 3000
|
||||
├── 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.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.tv/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
|
||||
|
||||
## Vercel Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"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:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"results": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
Error responses:
|
||||
|
||||
```json
|
||||
{
|
||||
"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 |
|
||||
@@ -0,0 +1,375 @@
|
||||
# API Endpoints Reference
|
||||
|
||||
Complete documentation for all 18 MiruroAPI endpoints.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Health Check](#health-check)
|
||||
- [Stats](#stats)
|
||||
- [Search](#search)
|
||||
- [Suggestions](#suggestions)
|
||||
- [Filter](#filter)
|
||||
- [Trending](#trending)
|
||||
- [Popular](#popular)
|
||||
- [Upcoming](#upcoming)
|
||||
- [Recent](#recent)
|
||||
- [Spotlight](#spotlight)
|
||||
- [Schedule](#schedule)
|
||||
- [Anime Info](#anime-info)
|
||||
- [Characters](#characters)
|
||||
- [Relations](#relations)
|
||||
- [Recommendations](#recommendations)
|
||||
- [Episodes](#episodes)
|
||||
- [Watch (Streaming)](#watch-streaming)
|
||||
|
||||
---
|
||||
|
||||
## Health Check
|
||||
|
||||
```
|
||||
GET /api/health
|
||||
```
|
||||
|
||||
Returns API health status, version, uptime, memory usage, and available providers.
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"results": {
|
||||
"status": "healthy",
|
||||
"version": "1.2.0",
|
||||
"uptime": "0h 0m 34s",
|
||||
"node": "v24.14.1",
|
||||
"memory": { "used": "13MB", "total": "15MB" },
|
||||
"endpoints": 16,
|
||||
"providers": ["kiwi","pewe","bee","bonk","bun","ally","nun","twin","cog","moo","hop","telli"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Stats
|
||||
|
||||
```
|
||||
GET /api/stats
|
||||
```
|
||||
|
||||
Returns request statistics, cache info, and memory usage.
|
||||
|
||||
---
|
||||
|
||||
## Search
|
||||
|
||||
```
|
||||
GET /api/search
|
||||
```
|
||||
|
||||
| Param | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `query` | string | Yes | — | Search keyword |
|
||||
| `page` | number | No | 1 | Page number |
|
||||
| `per_page` | number | No | 20 | Results per page |
|
||||
|
||||
**Example:** `GET /api/search?query=naruto&per_page=2`
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"results": {
|
||||
"page": 1,
|
||||
"perPage": 2,
|
||||
"total": 5000,
|
||||
"hasNextPage": true,
|
||||
"results": [
|
||||
{
|
||||
"id": 20,
|
||||
"title": { "romaji": "NARUTO", "english": "Naruto", "native": "NARUTO -ナルト-" },
|
||||
"coverImage": { "large": "https://s4.anilist.co/file/..." },
|
||||
"format": "TV",
|
||||
"episodes": 220,
|
||||
"status": "FINISHED",
|
||||
"averageScore": 80,
|
||||
"genres": ["Action","Adventure","Comedy","Drama","Fantasy","Supernatural"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Suggestions
|
||||
|
||||
```
|
||||
GET /api/suggestions
|
||||
```
|
||||
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `query` | string | Yes | Search keyword |
|
||||
|
||||
**Example:** `GET /api/suggestions?query=naruto`
|
||||
|
||||
Returns fast autocomplete suggestions with title, poster, format, status, year, and episode count.
|
||||
|
||||
---
|
||||
|
||||
## Filter
|
||||
|
||||
```
|
||||
GET /api/filter
|
||||
```
|
||||
|
||||
| Param | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `genre` | string | No | — | Genre name (e.g. "Action") |
|
||||
| `tag` | string | No | — | Tag name |
|
||||
| `year` | number | No | — | Release year |
|
||||
| `season` | string | No | — | FALL, WINTER, SPRING, SUMMER |
|
||||
| `format` | string | No | — | TV, MOVIE, OVA, ONA, SPECIAL, MUSIC |
|
||||
| `status` | string | No | — | RELEASING, FINISHED, NOT_YET_RELEASED, CANCELLED |
|
||||
| `sort` | string | No | POPULARITY_DESC | Sort order |
|
||||
| `page` | number | No | 1 | Page number |
|
||||
| `per_page` | number | No | 20 | Results per page |
|
||||
|
||||
**Example:** `GET /api/filter?genre=Action&year=2024&season=WINTER&per_page=3`
|
||||
|
||||
---
|
||||
|
||||
## Trending
|
||||
|
||||
```
|
||||
GET /api/trending
|
||||
```
|
||||
|
||||
| Param | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `per_page` | number | No | 20 | Results per page |
|
||||
|
||||
**Example:** `GET /api/trending?per_page=5`
|
||||
|
||||
---
|
||||
|
||||
## Popular
|
||||
|
||||
```
|
||||
GET /api/popular
|
||||
```
|
||||
|
||||
| Param | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `per_page` | number | No | 20 | Results per page |
|
||||
|
||||
---
|
||||
|
||||
## Upcoming
|
||||
|
||||
```
|
||||
GET /api/upcoming
|
||||
```
|
||||
|
||||
| Param | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `per_page` | number | No | 20 | Results per page |
|
||||
|
||||
---
|
||||
|
||||
## Recent
|
||||
|
||||
```
|
||||
GET /api/recent
|
||||
```
|
||||
|
||||
| Param | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `per_page` | number | No | 20 | Results per page |
|
||||
|
||||
---
|
||||
|
||||
## Spotlight
|
||||
|
||||
```
|
||||
GET /api/spotlight
|
||||
```
|
||||
|
||||
Returns featured/spotlight anime for the hero carousel.
|
||||
|
||||
---
|
||||
|
||||
## Schedule
|
||||
|
||||
```
|
||||
GET /api/schedule
|
||||
```
|
||||
|
||||
| Param | Type | Required | Default | Description |
|
||||
|-------|------|----------|---------|-------------|
|
||||
| `date` | string | No | today | Date in YYYY-MM-DD format |
|
||||
|
||||
---
|
||||
|
||||
## Anime Info
|
||||
|
||||
```
|
||||
GET /api/info/:id
|
||||
```
|
||||
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `id` | number | Yes | AniList anime ID |
|
||||
|
||||
**Example:** `GET /api/info/20`
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"results": {
|
||||
"id": 20,
|
||||
"idMal": 20,
|
||||
"title": { "romaji": "NARUTO", "english": "Naruto", "native": "NARUTO -ナルト-" },
|
||||
"description": "Naruto Uzumaki, a hyperactive and knuckle-headed ninja...",
|
||||
"coverImage": { "large": "https://s4.anilist.co/file/..." },
|
||||
"format": "TV",
|
||||
"episodes": 220,
|
||||
"duration": 23,
|
||||
"status": "FINISHED",
|
||||
"averageScore": 80,
|
||||
"genres": ["Action","Adventure","Comedy","Drama","Fantasy","Supernatural"],
|
||||
"studios": [{ "name": "Studio Pierrot", "isAnimationStudio": true }]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Characters
|
||||
|
||||
```
|
||||
GET /api/anime/:id/characters
|
||||
```
|
||||
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `id` | number | Yes | AniList anime ID |
|
||||
|
||||
**Example:** `GET /api/anime/20/characters`
|
||||
|
||||
Returns characters with voice actors.
|
||||
|
||||
---
|
||||
|
||||
## Relations
|
||||
|
||||
```
|
||||
GET /api/anime/:id/relations
|
||||
```
|
||||
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `id` | number | Yes | AniList anime ID |
|
||||
|
||||
Returns related anime (sequels, prequels, side stories, etc.).
|
||||
|
||||
---
|
||||
|
||||
## Recommendations
|
||||
|
||||
```
|
||||
GET /api/anime/:id/recommendations
|
||||
```
|
||||
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `id` | number | Yes | AniList anime ID |
|
||||
|
||||
Returns recommended anime based on the given ID.
|
||||
|
||||
---
|
||||
|
||||
## Episodes
|
||||
|
||||
```
|
||||
GET /api/episodes/:id
|
||||
```
|
||||
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `id` | number | Yes | AniList anime ID |
|
||||
|
||||
**Example:** `GET /api/episodes/20`
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"results": {
|
||||
"providers": {
|
||||
"kiwi": {
|
||||
"meta": { "id": "1571", "title": "Naruto", "type": "TV" },
|
||||
"episodes": {
|
||||
"sub": [
|
||||
{
|
||||
"id": "watch/kiwi/20/sub/animepahe-1",
|
||||
"number": 1,
|
||||
"title": "Enter: Naruto Uzumaki!",
|
||||
"airDate": "2002-10-03",
|
||||
"audio": "sub",
|
||||
"filler": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Watch (Streaming)
|
||||
|
||||
```
|
||||
GET /api/watch/:provider/:anilistId/:category/:slug
|
||||
```
|
||||
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `provider` | string | Yes | Provider name (kiwi, pewe, etc.) |
|
||||
| `anilistId` | number | Yes | AniList anime ID |
|
||||
| `category` | string | Yes | sub or dub |
|
||||
| `slug` | string | Yes | Episode slug from episodes response |
|
||||
|
||||
**Example:** `GET /api/watch/kiwi/20/sub/animepahe-1`
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"results": {
|
||||
"streams": [
|
||||
{
|
||||
"url": "https://vault-01.uwucdn.top/stream/.../uwu.m3u8",
|
||||
"type": "hls",
|
||||
"quality": "360p",
|
||||
"resolution": { "width": 640, "height": 360 },
|
||||
"codec": "h264",
|
||||
"audio": "sub",
|
||||
"fansub": "df68",
|
||||
"referer": "https://kwik.cx/e/..."
|
||||
}
|
||||
],
|
||||
"download": "https://pahe.win/LJmbA"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,267 @@
|
||||
# Code Examples
|
||||
|
||||
All examples are tested and working with the live API.
|
||||
|
||||
---
|
||||
|
||||
## cURL
|
||||
|
||||
### Search
|
||||
|
||||
```bash
|
||||
curl "https://mirurotvapi.vercel.app/api/search?query=naruto"
|
||||
```
|
||||
|
||||
### Suggestions
|
||||
|
||||
```bash
|
||||
curl "https://mirurotvapi.vercel.app/api/suggestions?query=naruto"
|
||||
```
|
||||
|
||||
### Anime Info
|
||||
|
||||
```bash
|
||||
curl "https://mirurotvapi.vercel.app/api/info/20"
|
||||
```
|
||||
|
||||
### Episodes
|
||||
|
||||
```bash
|
||||
curl "https://mirurotvapi.vercel.app/api/episodes/20"
|
||||
```
|
||||
|
||||
### Characters
|
||||
|
||||
```bash
|
||||
curl "https://mirurotvapi.vercel.app/api/anime/20/characters"
|
||||
```
|
||||
|
||||
### Stream URL
|
||||
|
||||
```bash
|
||||
curl "https://mirurotvapi.vercel.app/api/watch/kiwi/20/sub/animepahe-1"
|
||||
```
|
||||
|
||||
### Filter
|
||||
|
||||
```bash
|
||||
curl "https://mirurotvapi.vercel.app/api/filter?genre=Action&per_page=3"
|
||||
```
|
||||
|
||||
### Trending
|
||||
|
||||
```bash
|
||||
curl "https://mirurotvapi.vercel.app/api/trending?per_page=5"
|
||||
```
|
||||
|
||||
### Popular
|
||||
|
||||
```bash
|
||||
curl "https://mirurotvapi.vercel.app/api/popular?per_page=5"
|
||||
```
|
||||
|
||||
### Schedule
|
||||
|
||||
```bash
|
||||
curl "https://mirurotvapi.vercel.app/api/schedule?date=2026-06-09"
|
||||
```
|
||||
|
||||
### Health
|
||||
|
||||
```bash
|
||||
curl "https://mirurotvapi.vercel.app/api/health"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## JavaScript (Browser)
|
||||
|
||||
### Search
|
||||
|
||||
```javascript
|
||||
async function searchAnime(query) {
|
||||
const res = await fetch(`https://mirurotvapi.vercel.app/api/search?query=${encodeURIComponent(query)}`);
|
||||
const data = await res.json();
|
||||
|
||||
return data.results.results.map(anime => ({
|
||||
id: anime.id,
|
||||
title: anime.title.english || anime.title.romaji,
|
||||
format: anime.format,
|
||||
episodes: anime.episodes,
|
||||
score: anime.averageScore
|
||||
}));
|
||||
}
|
||||
|
||||
searchAnime('naruto').then(results => {
|
||||
results.forEach(a => console.log(`${a.title} (${a.format}) — Score: ${a.score}`));
|
||||
});
|
||||
```
|
||||
|
||||
### Anime Info
|
||||
|
||||
```javascript
|
||||
async function getAnimeInfo(anilistId) {
|
||||
const res = await fetch(`https://mirurotvapi.vercel.app/api/info/${anilistId}`);
|
||||
const data = await res.json();
|
||||
|
||||
const info = data.results;
|
||||
console.log(`Title: ${info.title.english || info.title.romaji}`);
|
||||
console.log(`Format: ${info.format}`);
|
||||
console.log(`Status: ${info.status}`);
|
||||
console.log(`Episodes: ${info.episodes || 'Unknown'}`);
|
||||
console.log(`Score: ${info.averageScore}`);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
getAnimeInfo(20);
|
||||
```
|
||||
|
||||
### Full Streaming Flow
|
||||
|
||||
```javascript
|
||||
async function getStreamUrl(anilistId) {
|
||||
// Step 1: Get episodes
|
||||
const episodesRes = await fetch(`https://mirurotvapi.vercel.app/api/episodes/${anilistId}`);
|
||||
const episodesData = await episodesRes.json();
|
||||
const providers = episodesData.results.providers;
|
||||
|
||||
const provider = Object.keys(providers)[0];
|
||||
const episodes = providers[provider].episodes.sub;
|
||||
|
||||
if (!episodes || episodes.length === 0) {
|
||||
throw new Error('No episodes found');
|
||||
}
|
||||
|
||||
const episodeId = episodes[0].id;
|
||||
const parts = episodeId.split('/');
|
||||
const category = parts[3];
|
||||
const slug = parts[4];
|
||||
|
||||
// Step 2: Get stream URLs
|
||||
const streamRes = await fetch(`https://mirurotvapi.vercel.app/api/watch/${provider}/${anilistId}/${category}/${slug}`);
|
||||
const streamData = await streamRes.json();
|
||||
|
||||
return streamData.results.streams[0].url;
|
||||
}
|
||||
|
||||
// Usage
|
||||
getStreamUrl(20)
|
||||
.then(url => {
|
||||
console.log('Stream URL:', url);
|
||||
// Use with HLS.js player
|
||||
})
|
||||
.catch(err => console.error('Error:', err.message));
|
||||
```
|
||||
|
||||
### Characters
|
||||
|
||||
```javascript
|
||||
async function getCharacters(anilistId) {
|
||||
const res = await fetch(`https://mirurotvapi.vercel.app/api/anime/${anilistId}/characters`);
|
||||
const data = await res.json();
|
||||
|
||||
return data.results.edges.map(edge => ({
|
||||
name: edge.node.name.full,
|
||||
role: edge.role,
|
||||
voiceActor: edge.voiceActors[0]?.name.full || 'N/A'
|
||||
}));
|
||||
}
|
||||
|
||||
getCharacters(20).then(chars => {
|
||||
chars.forEach(c => console.log(`${c.name} (${c.role}) — VA: ${c.voiceActor}`));
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Node.js (with axios)
|
||||
|
||||
### Search
|
||||
|
||||
```javascript
|
||||
const axios = require('axios');
|
||||
|
||||
async function searchAnime(query) {
|
||||
const { data } = await axios.get('https://mirurotvapi.vercel.app/api/search', {
|
||||
params: { query }
|
||||
});
|
||||
|
||||
return data.results.results;
|
||||
}
|
||||
|
||||
searchAnime('naruto').then(results => {
|
||||
results.forEach(a => console.log(`${a.title.english || a.title.romaji}`));
|
||||
});
|
||||
```
|
||||
|
||||
### Full Streaming Flow
|
||||
|
||||
```javascript
|
||||
const axios = require('axios');
|
||||
|
||||
async function getStreamUrl(anilistId) {
|
||||
const BASE = 'https://mirurotvapi.vercel.app/api';
|
||||
|
||||
// Step 1: Get episodes
|
||||
const { data: episodesData } = await axios.get(`${BASE}/episodes/${anilistId}`);
|
||||
const providers = episodesData.results.providers;
|
||||
|
||||
const provider = Object.keys(providers)[0];
|
||||
const episodes = providers[provider].episodes.sub;
|
||||
|
||||
if (!episodes || episodes.length === 0) {
|
||||
throw new Error('No episodes found');
|
||||
}
|
||||
|
||||
const episodeId = episodes[0].id;
|
||||
const parts = episodeId.split('/');
|
||||
const category = parts[3];
|
||||
const slug = parts[4];
|
||||
|
||||
// Step 2: Get stream URLs
|
||||
const { data: streamData } = await axios.get(`${BASE}/watch/${provider}/${anilistId}/${category}/${slug}`);
|
||||
|
||||
return streamData.results.streams[0].url;
|
||||
}
|
||||
|
||||
// Usage
|
||||
getStreamUrl(20)
|
||||
.then(url => console.log('Stream URL:', url))
|
||||
.catch(err => console.error('Error:', err.message));
|
||||
```
|
||||
|
||||
### Filter by Genre
|
||||
|
||||
```javascript
|
||||
const axios = require('axios');
|
||||
|
||||
async function filterAnime(genre, perPage = 5) {
|
||||
const { data } = await axios.get('https://mirurotvapi.vercel.app/api/filter', {
|
||||
params: { genre, per_page: perPage }
|
||||
});
|
||||
|
||||
return data.results.results;
|
||||
}
|
||||
|
||||
filterAnime('Action', 3).then(anime => {
|
||||
anime.forEach(a => console.log(`${a.title.english || a.title.romaji} (Score: ${a.averageScore})`));
|
||||
});
|
||||
```
|
||||
|
||||
### Schedule
|
||||
|
||||
```javascript
|
||||
const axios = require('axios');
|
||||
|
||||
async function getSchedule(date) {
|
||||
const { data } = await axios.get('https://mirurotvapi.vercel.app/api/schedule', {
|
||||
params: { date }
|
||||
});
|
||||
|
||||
console.log('Scheduled anime:', data.results.length);
|
||||
return data.results;
|
||||
}
|
||||
|
||||
getSchedule('2026-06-09');
|
||||
```
|
||||
@@ -0,0 +1,91 @@
|
||||
# MiruroAPI Documentation
|
||||
|
||||
> Free REST API for anime streaming data. 18 endpoints, 12 providers, M3U8 URLs. No API key required.
|
||||
|
||||
## Overview
|
||||
|
||||
MiruroAPI is a free, open-source REST API that provides anime data by aggregating from AniList GraphQL and Miruro streaming providers. It's built with Node.js and Express, and deployed on Vercel.
|
||||
|
||||
**Base URL:** `https://mirurotvapi.vercel.app/api`
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Search for anime
|
||||
curl "https://mirurotvapi.vercel.app/api/search?query=naruto"
|
||||
|
||||
# Get anime info
|
||||
curl "https://mirurotvapi.vercel.app/api/info/20"
|
||||
|
||||
# Get episodes
|
||||
curl "https://mirurotvapi.vercel.app/api/episodes/20"
|
||||
```
|
||||
|
||||
**Live Response — Search:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"results": {
|
||||
"page": 1,
|
||||
"perPage": 20,
|
||||
"total": 5000,
|
||||
"hasNextPage": true,
|
||||
"results": [
|
||||
{
|
||||
"id": 20,
|
||||
"title": { "romaji": "NARUTO", "english": "Naruto" },
|
||||
"coverImage": { "large": "https://s4.anilist.co/file/..." },
|
||||
"format": "TV",
|
||||
"episodes": 220,
|
||||
"status": "FINISHED",
|
||||
"averageScore": 80
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- **18 Endpoints** — Search, info, episodes, streaming, characters, relations, and more
|
||||
- **12 Streaming Providers** — M3U8 URLs with resolution, codec, fansub info
|
||||
- **No API Key** — Just make requests, no registration needed
|
||||
- **In-Memory Cache** — 1-5 minute TTL for fast responses
|
||||
- **CORS Enabled** — Access from any domain
|
||||
- **JSON Responses** — Standardized `{success, results}` format
|
||||
|
||||
## Documentation
|
||||
|
||||
- [API Endpoints Reference](endpoints.md) — Complete endpoint documentation with real responses
|
||||
- [Streaming Flow Guide](streaming.md) — How to get stream URLs step by step
|
||||
- [Code Examples](examples.md) — cURL, JavaScript, Python (all tested and working)
|
||||
- [Architecture](architecture.md) — Project structure and design decisions
|
||||
|
||||
## Response Format
|
||||
|
||||
All endpoints return JSON in this format:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"results": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
Error responses:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Error description"
|
||||
}
|
||||
```
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
Rate limiting is enforced at 100 requests per minute per IP address. The API also uses a 1-5 minute in-memory cache. Repeated requests for the same data will be served from cache.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This API is for **educational purposes only**. It fetches data from AniList GraphQL and Miruro streaming providers. We are not affiliated with or endorsed by Miruro. All content belongs to its respective owners.
|
||||
@@ -0,0 +1,238 @@
|
||||
# Streaming Flow Guide
|
||||
|
||||
This guide explains how to get streaming URLs from MiruroAPI in 3 steps.
|
||||
|
||||
## Overview
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Get Episodes
|
||||
|
||||
Each anime has episodes from multiple providers. Fetch the episode list with the AniList ID.
|
||||
|
||||
**Request:**
|
||||
|
||||
```bash
|
||||
curl "https://mirurotvapi.vercel.app/api/episodes/20"
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"results": {
|
||||
"providers": {
|
||||
"kiwi": {
|
||||
"meta": { "id": "1571", "title": "Naruto", "type": "TV" },
|
||||
"episodes": {
|
||||
"sub": [
|
||||
{
|
||||
"id": "watch/kiwi/20/sub/animepahe-1",
|
||||
"number": 1,
|
||||
"title": "Enter: Naruto Uzumaki!",
|
||||
"image": "https://image.tmdb.org/t/p/original/...",
|
||||
"airDate": "2002-10-03",
|
||||
"audio": "sub",
|
||||
"filler": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Key field:** `id` in each episode — Pass this to the next step.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Get Stream URLs
|
||||
|
||||
Use the episode `id` from Step 1 to get M3U8 streaming URLs.
|
||||
|
||||
**Request:**
|
||||
|
||||
```bash
|
||||
curl "https://mirurotvapi.vercel.app/api/watch/kiwi/20/sub/animepahe-1"
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"results": {
|
||||
"streams": [
|
||||
{
|
||||
"url": "https://vault-01.uwucdn.top/stream/.../uwu.m3u8",
|
||||
"type": "hls",
|
||||
"quality": "360p",
|
||||
"resolution": { "width": 640, "height": 360 },
|
||||
"codec": "h264",
|
||||
"audio": "sub",
|
||||
"fansub": "df68",
|
||||
"isActive": false,
|
||||
"referer": "https://kwik.cx/e/..."
|
||||
}
|
||||
],
|
||||
"download": "https://pahe.win/LJmbA"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Done!** Use `streams[0].url` in your HLS video player.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Play M3U8
|
||||
|
||||
### HLS.js (Browser)
|
||||
|
||||
```html
|
||||
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
|
||||
<video id="player" controls></video>
|
||||
|
||||
<script>
|
||||
const url = 'https://vault-01.uwucdn.top/stream/.../uwu.m3u8';
|
||||
const video = document.getElementById('player');
|
||||
|
||||
if (Hls.isSupported()) {
|
||||
const hls = new Hls();
|
||||
hls.loadSource(url);
|
||||
hls.attachMedia(video);
|
||||
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
video.src = url; // Native HLS (Safari)
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## JavaScript Example
|
||||
|
||||
```javascript
|
||||
async function getStreamUrl(anilistId) {
|
||||
// Step 1: Get episodes
|
||||
const episodesRes = await fetch(`https://mirurotvapi.vercel.app/api/episodes/${anilistId}`);
|
||||
const episodesData = await episodesRes.json();
|
||||
const providers = episodesData.results.providers;
|
||||
|
||||
// Pick first provider with sub episodes
|
||||
const provider = Object.keys(providers)[0];
|
||||
const episodes = providers[provider].episodes.sub;
|
||||
|
||||
if (!episodes || episodes.length === 0) {
|
||||
throw new Error('No episodes found');
|
||||
}
|
||||
|
||||
// Use first episode's id
|
||||
const episodeId = episodes[0].id; // "watch/kiwi/20/sub/animepahe-1"
|
||||
const parts = episodeId.split('/');
|
||||
const category = parts[3]; // "sub"
|
||||
const slug = parts[4]; // "animepahe-1"
|
||||
|
||||
// Step 2: Get stream URLs
|
||||
const streamRes = await fetch(`https://mirurotvapi.vercel.app/api/watch/${provider}/${anilistId}/${category}/${slug}`);
|
||||
const streamData = await streamRes.json();
|
||||
|
||||
return streamData.results.streams[0].url; // "https://...m3u8"
|
||||
}
|
||||
|
||||
// Usage
|
||||
getStreamUrl(20)
|
||||
.then(url => console.log('Stream URL:', url))
|
||||
.catch(err => console.error('Error:', err));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Python Example
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
def get_stream_url(anilist_id):
|
||||
base = "https://mirurotvapi.vercel.app/api"
|
||||
|
||||
# Step 1: Get episodes
|
||||
episodes_res = requests.get(f"{base}/episodes/{anilist_id}")
|
||||
episodes_data = episodes_res.json()
|
||||
providers = episodes_data['results']['providers']
|
||||
|
||||
# Pick first provider
|
||||
provider = list(providers.keys())[0]
|
||||
episodes = providers[provider]['episodes']['sub']
|
||||
|
||||
if not episodes:
|
||||
raise Exception("No episodes found")
|
||||
|
||||
episode_id = episodes[0]['id'] # "watch/kiwi/20/sub/animepahe-1"
|
||||
parts = episode_id.split('/')
|
||||
category = parts[3]
|
||||
slug = parts[4]
|
||||
|
||||
# Step 2: Get stream URLs
|
||||
stream_res = requests.get(f"{base}/watch/{provider}/{anilist_id}/{category}/{slug}")
|
||||
stream_data = stream_res.json()
|
||||
|
||||
return stream_data['results']['streams'][0]['url']
|
||||
|
||||
# Usage
|
||||
url = get_stream_url(20)
|
||||
print(f"Stream URL: {url}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Sub & Dub
|
||||
|
||||
Providers return both `sub` and `dub` episode lists:
|
||||
|
||||
```javascript
|
||||
const eps = await fetch("/api/episodes/20").then(r => r.json());
|
||||
const kiwi = eps.results.providers.kiwi.episodes;
|
||||
|
||||
// Get sub episodes
|
||||
const subEps = kiwi.sub; // [{ id: "watch/kiwi/20/sub/animepahe-1", ... }]
|
||||
|
||||
// Get dub episodes (if available)
|
||||
const dubEps = kiwi.dub; // [{ id: "watch/kiwi/20/dub/...", ... }]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Available Providers
|
||||
|
||||
| Provider | Sub | Dub | Download | Skip Times |
|
||||
|:---|:---:|:---:|:---:|:---:|
|
||||
| kiwi | ✅ | ❌ | ✅ | ❌ |
|
||||
| pewe | ✅ | ❌ | ❌ | ❌ |
|
||||
| bee | ✅ | ✅ | ❌ | ❌ |
|
||||
| bonk | ✅ | ✅ | ✅ | ✅ |
|
||||
| bun | ✅ | ✅ | ❌ | ❌ |
|
||||
| ally | ✅ | ❌ | ✅ | ❌ |
|
||||
| nun | ✅ | ❌ | ❌ | ❌ |
|
||||
| twin | ✅ | ✅ | ❌ | ❌ |
|
||||
| cog | ✅ | ❌ | ❌ | ❌ |
|
||||
| moo | ✅ | ❌ | ✅ | ❌ |
|
||||
| hop | ❌ | ✅ | ❌ | ❌ |
|
||||
| telli | ✅ | ❌ | ❌ | ❌ |
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Solution |
|
||||
|-------|----------|
|
||||
| "No episodes found" | Check the AniList ID is correct (use `/api/search` first) |
|
||||
| "No providers" | The anime may not be available on any provider |
|
||||
| Stream URL 403 | Some servers require specific referrer headers |
|
||||
| CORS error | Use a proxy or access from server-side |
|
||||
Reference in New Issue
Block a user