mirror of
https://github.com/Shineii86/MiruroAPI.git
synced 2026-10-22 02:05:10 +00:00
feat: v1.1.0 - Swagger docs, mappings, Docker, premium landing page
- Swagger UI interactive docs at /docs - OpenAPI 3.0 spec at /openapi.json - mappings field in episodes response (anilistId, malId, kitsuId) - Docker support (Dockerfile + .dockerignore) - Premium landing page with try-it buttons (Walter-style) - Health endpoint with providers list and endpoint count
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
.env
|
||||||
|
.vercel
|
||||||
|
.git
|
||||||
@@ -1,5 +1,15 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.1.0
|
||||||
|
- Added Swagger UI interactive docs at /docs
|
||||||
|
- Added OpenAPI 3.0 spec at /openapi.json
|
||||||
|
- Added mappings field in episodes response (anilistId, malId, kitsuId)
|
||||||
|
- Added Docker support (Dockerfile + .dockerignore)
|
||||||
|
- Improved landing page with premium design (Walter-style)
|
||||||
|
- Added "Try it" buttons on all endpoints
|
||||||
|
- Health endpoint now includes version, providers list, and endpoint count
|
||||||
|
- Version bump to 1.1.0
|
||||||
|
|
||||||
## v1.0.0
|
## v1.0.0
|
||||||
- Initial release
|
- Initial release
|
||||||
- AniList GraphQL integration for anime metadata
|
- AniList GraphQL integration for anime metadata
|
||||||
|
|||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
FROM node:20-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install --production
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD ["node", "server.js"]
|
||||||
@@ -5,6 +5,8 @@ Free RESTful API for anime streaming data. Powered by AniList GraphQL and Miruro
|
|||||||
## Live
|
## Live
|
||||||
|
|
||||||
- **API**: `https://miruroapi-zeta.vercel.app`
|
- **API**: `https://miruroapi-zeta.vercel.app`
|
||||||
|
- **Docs**: `https://miruroapi-zeta.vercel.app/docs` (Swagger UI)
|
||||||
|
- **OpenAPI**: `https://miruroapi-zeta.vercel.app/openapi.json`
|
||||||
- **GitHub**: `https://github.com/Shineii86/MiruroAPI`
|
- **GitHub**: `https://github.com/Shineii86/MiruroAPI`
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@@ -13,29 +15,49 @@ Free RESTful API for anime streaming data. Powered by AniList GraphQL and Miruro
|
|||||||
- AniList GraphQL for metadata (search, trending, info, filter, schedule)
|
- AniList GraphQL for metadata (search, trending, info, filter, schedule)
|
||||||
- 12 streaming providers (kiwi, pewe, bee, bonk, bun, ally, nun, twin, cog, moo, hop, telli)
|
- 12 streaming providers (kiwi, pewe, bee, bonk, bun, ally, nun, twin, cog, moo, hop, telli)
|
||||||
- M3U8 streaming URLs with subtitles and skip timestamps
|
- M3U8 streaming URLs with subtitles and skip timestamps
|
||||||
- In-memory caching
|
- In-memory caching with TTL
|
||||||
- CORS enabled
|
- CORS enabled
|
||||||
|
- Swagger UI interactive docs
|
||||||
|
- Docker support
|
||||||
|
- Health endpoint
|
||||||
|
|
||||||
## Endpoints
|
## Endpoints
|
||||||
|
|
||||||
|
### Search & Discovery
|
||||||
|
|
||||||
| Endpoint | Description |
|
| Endpoint | Description |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `GET /api/search?query=naruto` | Search anime by name |
|
| `GET /api/search?query=naruto` | Search anime by name |
|
||||||
| `GET /api/suggestions?query=naruto` | Autocomplete suggestions |
|
| `GET /api/suggestions?query=naruto` | Autocomplete suggestions |
|
||||||
|
| `GET /api/filter?genre=Action` | Advanced filter |
|
||||||
|
|
||||||
|
### Collections
|
||||||
|
|
||||||
|
| Endpoint | Description |
|
||||||
|
|---|---|
|
||||||
| `GET /api/trending` | Trending anime |
|
| `GET /api/trending` | Trending anime |
|
||||||
| `GET /api/popular` | Most popular anime |
|
| `GET /api/popular` | Most popular anime |
|
||||||
| `GET /api/upcoming` | Upcoming anime |
|
| `GET /api/upcoming` | Upcoming anime |
|
||||||
| `GET /api/recent` | Currently airing anime |
|
| `GET /api/recent` | Currently airing anime |
|
||||||
| `GET /api/spotlight` | Spotlight/featured anime |
|
| `GET /api/spotlight` | Spotlight/featured anime |
|
||||||
| `GET /api/schedule` | Airing schedule |
|
| `GET /api/schedule` | Airing schedule |
|
||||||
| `GET /api/filter?genre=Action` | Advanced filter |
|
|
||||||
|
### Anime Details
|
||||||
|
|
||||||
|
| Endpoint | Description |
|
||||||
|
|---|---|
|
||||||
| `GET /api/info/:id` | Full anime info by AniList ID |
|
| `GET /api/info/:id` | Full anime info by AniList ID |
|
||||||
| `GET /api/anime/:id/characters` | Characters with voice actors |
|
| `GET /api/anime/:id/characters` | Characters with voice actors |
|
||||||
| `GET /api/anime/:id/relations` | Related anime |
|
| `GET /api/anime/:id/relations` | Related anime |
|
||||||
| `GET /api/anime/:id/recommendations` | Recommendations |
|
| `GET /api/anime/:id/recommendations` | Recommendations |
|
||||||
|
|
||||||
|
### Streaming
|
||||||
|
|
||||||
|
| Endpoint | Description |
|
||||||
|
|---|---|
|
||||||
| `GET /api/episodes/:id` | Episode list from all providers |
|
| `GET /api/episodes/:id` | Episode list from all providers |
|
||||||
| `GET /api/sources?episodeId=...&provider=...&anilistId=...` | Streaming sources |
|
| `GET /api/sources?episodeId=...&provider=...&anilistId=...` | Streaming sources (detailed) |
|
||||||
| `GET /api/watch/:provider/:anilistId/:category/:slug` | Simple streaming sources |
|
| `GET /api/watch/:provider/:anilistId/:category/:slug` | Streaming sources (simple) |
|
||||||
|
|
||||||
## Streaming Flow
|
## Streaming Flow
|
||||||
|
|
||||||
@@ -50,6 +72,13 @@ npm install
|
|||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t miruroapi .
|
||||||
|
docker run -p 3000:3000 miruroapi
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
|
|||||||
+191
-31
@@ -3,43 +3,203 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>MiruroAPI - Free Anime Streaming API</title>
|
<title>MiruroAPI — Free Anime Streaming API</title>
|
||||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' style='stop-color:%2326A3D6'/%3E%3Cstop offset='100%25' style='stop-color:%237B2FF7'/%3E%3C/linearGradient%3E%3C/defs%3E%3Ccircle cx='50' cy='50' r='45' fill='url(%23g)'/%3E%3Ctext x='50' y='62' text-anchor='middle' fill='white' font-family='Arial Black' font-size='32' font-weight='bold'%3EM%3C/text%3E%3C/svg%3E">
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' style='stop-color:%2338bdf8'/%3E%3Cstop offset='100%25' style='stop-color:%23818cf8'/%3E%3C/linearGradient%3E%3C/defs%3E%3Ccircle cx='50' cy='50' r='45' fill='url(%23g)'/%3E%3Ctext x='50' y='62' text-anchor='middle' fill='white' font-family='Arial Black' font-size='32' font-weight='bold'%3EM%3C/text%3E%3C/svg%3E">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;500;700&display=swap" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
body { font-family: 'Segoe UI', system-ui, sans-serif; background: #0b1622; color: #e1e7ef; min-height: 100vh; display: flex; align-items: center; justify-content: center; }
|
body { font-family: 'Outfit', sans-serif; background: radial-gradient(circle at top, #0f172a, #020617); color: #e2e8f0; min-height: 100vh; padding: 50px 20px; }
|
||||||
.container { max-width: 800px; padding: 40px; text-align: center; }
|
.container { max-width: 960px; margin: 0 auto; background: rgba(30, 41, 59, 0.5); backdrop-filter: blur(10px); padding: 40px; border-radius: 24px; border: 1px solid rgba(255, 255, 255, 0.05); box-shadow: 0 20px 40px rgba(0,0,0,0.5); }
|
||||||
h1 { font-size: 2.5em; background: linear-gradient(135deg, #26A3D6, #7B2FF7); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 16px; }
|
.header { text-align: center; margin-bottom: 50px; }
|
||||||
p { color: #a0b1c5; font-size: 1.1em; margin-bottom: 24px; line-height: 1.6; }
|
.logo { width: 120px; border-radius: 20px; box-shadow: 0 0 30px rgba(56, 189, 248, 0.3); border: 1px solid rgba(255,255,255,0.1); margin-bottom: 25px; }
|
||||||
.badge { display: inline-block; background: rgba(38,163,214,0.15); color: #26A3D6; padding: 6px 16px; border-radius: 20px; font-size: 0.9em; margin-bottom: 24px; border: 1px solid rgba(38,163,214,0.3); }
|
h1 { font-size: 3em; font-weight: 700; background: linear-gradient(to right, #38bdf8, #818cf8); -webkit-background-clip: text; color: transparent; margin-bottom: 10px; }
|
||||||
.endpoints { text-align: left; background: rgba(20,32,48,0.8); border-radius: 12px; padding: 24px; margin-top: 24px; border: 1px solid rgba(255,255,255,0.05); }
|
.subtitle { color: #94a3b8; font-size: 1.1em; font-weight: 300; }
|
||||||
.endpoints h3 { color: #26A3D6; margin-bottom: 12px; }
|
.version { display: inline-block; background: rgba(56, 189, 248, 0.15); color: #38bdf8; padding: 4px 14px; border-radius: 20px; font-size: 0.85em; margin-top: 10px; border: 1px solid rgba(56, 189, 248, 0.2); }
|
||||||
.endpoint { padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,0.03); font-family: monospace; font-size: 0.9em; }
|
.section-title { font-size: 1.3em; font-weight: 700; color: #818cf8; margin: 35px 0 15px; border-left: 3px solid #818cf8; padding-left: 12px; }
|
||||||
.endpoint:last-child { border-bottom: none; }
|
.endpoint { background: rgba(15, 23, 42, 0.8); border-left: 4px solid #38bdf8; padding: 20px 25px; margin: 12px 0; border-radius: 0 16px 16px 0; border: 1px solid rgba(255,255,255,0.02); transition: all 0.3s ease; }
|
||||||
.method { color: #10b981; font-weight: bold; }
|
.endpoint:hover { transform: translateX(5px); box-shadow: 0 10px 20px rgba(0,0,0,0.2); border-left-color: #818cf8; background: rgba(30, 41, 59, 0.9); }
|
||||||
a { color: #26A3D6; text-decoration: none; }
|
.method { color: #10b981; font-weight: 700; background: rgba(16, 185, 129, 0.1); padding: 4px 10px; border-radius: 6px; font-size: 0.9em; margin-right: 10px; }
|
||||||
a:hover { text-decoration: underline; }
|
.url { font-family: monospace; color: #cbd5e1; font-size: 1.05em; }
|
||||||
|
.desc { color: #94a3b8; font-size: 0.95em; margin-top: 8px; font-weight: 300; line-height: 1.5; }
|
||||||
|
.badge { display: inline-block; font-size: 0.7em; padding: 2px 8px; border-radius: 6px; margin-left: 8px; font-weight: 500; vertical-align: middle; }
|
||||||
|
.badge-new { background: rgba(16, 185, 129, 0.15); color: #10b981; border: 1px solid rgba(16, 185, 129, 0.3); }
|
||||||
|
.step-num { display: inline-block; background: rgba(56, 189, 248, 0.15); color: #38bdf8; width: 26px; height: 26px; text-align: center; line-height: 26px; border-radius: 50%; font-size: 0.85em; font-weight: 700; margin-right: 8px; }
|
||||||
|
.note { background: rgba(250, 204, 21, 0.08); border: 1px solid rgba(250, 204, 21, 0.15); border-radius: 10px; padding: 14px 18px; margin-top: 12px; font-size: 0.88em; color: #fbbf24; line-height: 1.5; }
|
||||||
|
.note b { color: #fde68a; }
|
||||||
|
pre.snippet { background: #020617; padding: 14px; border-radius: 10px; margin-top: 12px; color: #a5b4fc; font-family: monospace; font-size: 0.82em; border: 1px solid rgba(255,255,255,0.05); overflow-x: auto; line-height: 1.5; }
|
||||||
|
table.param-table { width: 100%; margin-top: 12px; border-collapse: collapse; font-size: 0.85em; }
|
||||||
|
table.param-table th { text-align: left; color: #818cf8; font-weight: 500; padding: 6px 10px; border-bottom: 1px solid rgba(255,255,255,0.08); }
|
||||||
|
table.param-table td { padding: 6px 10px; color: #94a3b8; border-bottom: 1px solid rgba(255,255,255,0.03); }
|
||||||
|
table.param-table td:first-child { color: #a5b4fc; font-family: monospace; white-space: nowrap; }
|
||||||
|
.footer { text-align: center; margin-top: 50px; color: #475569; font-size: 0.9em; border-top: 1px solid rgba(255,255,255,0.05); padding-top: 20px; }
|
||||||
|
a { color: #38bdf8; text-decoration: none; font-weight: 500; }
|
||||||
|
a:hover { color: #818cf8; text-shadow: 0 0 10px rgba(129, 140, 248, 0.5); }
|
||||||
|
.try-btn { display: inline-block; background: rgba(56, 189, 248, 0.1); color: #38bdf8; padding: 6px 14px; border-radius: 8px; font-size: 0.85em; margin-top: 10px; border: 1px solid rgba(56, 189, 248, 0.2); transition: all 0.3s; }
|
||||||
|
.try-btn:hover { background: rgba(56, 189, 248, 0.2); transform: translateY(-1px); }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="badge">v1.0.0</div>
|
<div class="header">
|
||||||
<h1>MiruroAPI</h1>
|
<svg class="logo" viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg">
|
||||||
<p>Free RESTful API for anime streaming data. Powered by <a href="https://anilist.co">AniList GraphQL</a> and <a href="https://miruro.to">Miruro</a> streaming providers.</p>
|
<defs>
|
||||||
<div class="endpoints">
|
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||||
<h3>Endpoints</h3>
|
<stop offset="0%" style="stop-color:#38bdf8"/>
|
||||||
<div class="endpoint"><span class="method">GET</span> /api/search?query=naruto</div>
|
<stop offset="100%" style="stop-color:#818cf8"/>
|
||||||
<div class="endpoint"><span class="method">GET</span> /api/trending</div>
|
</linearGradient>
|
||||||
<div class="endpoint"><span class="method">GET</span> /api/popular</div>
|
</defs>
|
||||||
<div class="endpoint"><span class="method">GET</span> /api/upcoming</div>
|
<rect width="120" height="120" rx="24" fill="url(#grad)"/>
|
||||||
<div class="endpoint"><span class="method">GET</span> /api/recent</div>
|
<text x="60" y="75" text-anchor="middle" fill="white" font-family="Arial Black,sans-serif" font-size="48" font-weight="900">M</text>
|
||||||
<div class="endpoint"><span class="method">GET</span> /api/spotlight</div>
|
</svg>
|
||||||
<div class="endpoint"><span class="method">GET</span> /api/schedule</div>
|
<h1>MiruroAPI</h1>
|
||||||
<div class="endpoint"><span class="method">GET</span> /api/filter?genre=Action</div>
|
<div class="subtitle">Decrypted, bypassed, and reverse-engineered anime streaming API</div>
|
||||||
<div class="endpoint"><span class="method">GET</span> /api/info/:id</div>
|
<div class="version">v1.1.0 — Full Data, Streaming & Pagination</div>
|
||||||
<div class="endpoint"><span class="method">GET</span> /api/episodes/:id</div>
|
</div>
|
||||||
<div class="endpoint"><span class="method">GET</span> /api/watch/:provider/:id/:category/:slug</div>
|
|
||||||
<div class="endpoint"><span class="method">GET</span> /api/health</div>
|
<div class="note" style="background: rgba(16, 185, 129, 0.08); border-color: rgba(16, 185, 129, 0.2); color: #10b981;">
|
||||||
|
<b>16 Endpoints</b> — Powered by AniList GraphQL + Miruro streaming pipe. M3U8 URLs, subtitles, intro/outro timestamps.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SEARCH & DISCOVERY -->
|
||||||
|
<div class="section-title">Search & Discovery</div>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<div><span class="method">GET</span> <span class="url">/api/search?query={name}</span></div>
|
||||||
|
<div class="desc">Full-text anime search with rich metadata — title, cover, genres, studios, scores, airing status. 20+ fields per result.</div>
|
||||||
|
<a class="try-btn" href="/api/search?query=naruto&per_page=3" target="_blank">Try it</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<div><span class="method">GET</span> <span class="url">/api/suggestions?query={name}</span> <span class="badge badge-new">NEW</span></div>
|
||||||
|
<div class="desc">Lightweight autocomplete for dropdowns — id, title, poster, format, status, year. Max 8 results.</div>
|
||||||
|
<a class="try-btn" href="/api/suggestions?query=one+piece" target="_blank">Try it</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<div><span class="method">GET</span> <span class="url">/api/filter</span> <span class="badge badge-new">NEW</span></div>
|
||||||
|
<div class="desc">Advanced filter by genre, tag, year, season, format, status, sort. All params optional.</div>
|
||||||
|
<table class="param-table">
|
||||||
|
<tr><th>Param</th><th>Values</th></tr>
|
||||||
|
<tr><td>genre</td><td>Action, Romance, Comedy, Drama, Fantasy, Sci-Fi, etc.</td></tr>
|
||||||
|
<tr><td>tag</td><td>Isekai, Time Skip, Reincarnation, etc.</td></tr>
|
||||||
|
<tr><td>year</td><td>2025, 2024, etc.</td></tr>
|
||||||
|
<tr><td>season</td><td>WINTER · SPRING · SUMMER · FALL</td></tr>
|
||||||
|
<tr><td>format</td><td>TV · MOVIE · OVA · ONA · SPECIAL</td></tr>
|
||||||
|
<tr><td>status</td><td>RELEASING · FINISHED · NOT_YET_RELEASED · CANCELLED</td></tr>
|
||||||
|
<tr><td>sort</td><td>SCORE_DESC · POPULARITY_DESC · TRENDING_DESC · START_DATE_DESC</td></tr>
|
||||||
|
</table>
|
||||||
|
<a class="try-btn" href="/api/filter?genre=Action&format=TV&sort=SCORE_DESC&per_page=3" target="_blank">Try it</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- COLLECTIONS -->
|
||||||
|
<div class="section-title">Collections (All Paginated)</div>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<div><span class="method">GET</span> <span class="url">/api/trending</span></div>
|
||||||
|
<div class="desc">Currently trending anime across the community.</div>
|
||||||
|
<a class="try-btn" href="/api/trending?per_page=3" target="_blank">Try it</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<div><span class="method">GET</span> <span class="url">/api/popular</span></div>
|
||||||
|
<div class="desc">Most popular anime of all time by total user count.</div>
|
||||||
|
<a class="try-btn" href="/api/popular?per_page=3" target="_blank">Try it</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<div><span class="method">GET</span> <span class="url">/api/upcoming</span></div>
|
||||||
|
<div class="desc">Most anticipated anime that haven't aired yet.</div>
|
||||||
|
<a class="try-btn" href="/api/upcoming?per_page=3" target="_blank">Try it</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<div><span class="method">GET</span> <span class="url">/api/recent</span></div>
|
||||||
|
<div class="desc">Currently airing / this season's anime.</div>
|
||||||
|
<a class="try-btn" href="/api/recent?per_page=3" target="_blank">Try it</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<div><span class="method">GET</span> <span class="url">/api/spotlight</span> <span class="badge badge-new">HOT</span></div>
|
||||||
|
<div class="desc">Top 10 trending + popular anime. Perfect for hero banners.</div>
|
||||||
|
<a class="try-btn" href="/api/spotlight" target="_blank">Try it</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<div><span class="method">GET</span> <span class="url">/api/schedule</span></div>
|
||||||
|
<div class="desc">Upcoming episodes with UNIX timestamps and full anime metadata.</div>
|
||||||
|
<a class="try-btn" href="/api/schedule?per_page=3" target="_blank">Try it</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ANIME DETAILS -->
|
||||||
|
<div class="section-title">Anime Details</div>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<div><span class="method">GET</span> <span class="url">/api/info/{anilist_id}</span></div>
|
||||||
|
<div class="desc">Complete anime page — everything AniList has to offer. Characters (25), staff, relations, recommendations, trailer, stats, external links.</div>
|
||||||
|
<a class="try-btn" href="/api/info/21" target="_blank">Try it (One Piece)</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<div><span class="method">GET</span> <span class="url">/api/anime/{id}/characters</span></div>
|
||||||
|
<div class="desc">Paginated character list with Japanese voice actors, images, roles.</div>
|
||||||
|
<a class="try-btn" href="/api/anime/20/characters" target="_blank">Try it (Naruto)</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<div><span class="method">GET</span> <span class="url">/api/anime/{id}/relations</span></div>
|
||||||
|
<div class="desc">Sequels, prequels, side stories, spin-offs, source material.</div>
|
||||||
|
<a class="try-btn" href="/api/anime/20/relations" target="_blank">Try it</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="endpoint">
|
||||||
|
<div><span class="method">GET</span> <span class="url">/api/anime/{id}/recommendations</span></div>
|
||||||
|
<div class="desc">Community recommendations sorted by highest rating.</div>
|
||||||
|
<a class="try-btn" href="/api/anime/20/recommendations" target="_blank">Try it</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- STREAMING -->
|
||||||
|
<div class="section-title">Streaming (3-Step Flow)</div>
|
||||||
|
|
||||||
|
<div class="note">
|
||||||
|
<b>How streaming works:</b> To get a video URL, follow these 3 steps in order. Each step's output feeds into the next.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="endpoint" style="border-left-color: #10b981;">
|
||||||
|
<div><span class="step-num">1</span><span class="method">GET</span> <span class="url">/api/episodes/{anilist_id}</span></div>
|
||||||
|
<div class="desc">Get all episodes from multiple providers (kiwi, bee, bonk, ally, moo, etc.) organized by sub/dub. Returns episode IDs for Step 2.</div>
|
||||||
|
<pre class="snippet">{
|
||||||
|
"providers": {
|
||||||
|
"kiwi": { "episodes": { "sub": [{ "id": "watch/kiwi/20/sub/animepahe-1", "number": 1, ... }] } },
|
||||||
|
"bee": { ... },
|
||||||
|
"bonk": { ... }
|
||||||
|
}
|
||||||
|
}</pre>
|
||||||
|
<a class="try-btn" href="/api/episodes/20" target="_blank">Try it (Naruto)</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="endpoint" style="border-left-color: #10b981; background: rgba(16, 185, 129, 0.05);">
|
||||||
|
<div><span class="step-num">2</span> <span class="url">/api/watch/{provider}/{anilistId}/{category}/{slug}</span> <span class="badge badge-new">RECOMMENDED</span></div>
|
||||||
|
<div class="desc">Take the <b>id</b> from Step 1 and use it as the URL. Returns M3U8 streams, subtitles, intro/outro timestamps.</div>
|
||||||
|
<pre class="snippet">{
|
||||||
|
"streams": [
|
||||||
|
{ "url": "https://.../master.m3u8", "type": "hls", "quality": "1080p" }
|
||||||
|
],
|
||||||
|
"subtitles": [ { "file": "...", "label": "English" } ],
|
||||||
|
"intro": { "start": 0, "end": 90 },
|
||||||
|
"outro": { "start": 1300, "end": 1420 }
|
||||||
|
}</pre>
|
||||||
|
<a class="try-btn" href="/api/watch/kiwi/20/sub/animepahe-1" target="_blank">Try it</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="endpoint" style="border-left-color: #818cf8;">
|
||||||
|
<div><span class="step-num">3</span> <span style="color: #818cf8; font-weight: 500;">Play the stream</span></div>
|
||||||
|
<div class="desc">Feed <code>streams[0].url</code> into any HLS player (Video.js, hls.js, VLC, mpv). Use intro/outro timestamps for skip buttons.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
All collection endpoints return: <code style="color: #a5b4fc;">{ page, perPage, total, hasNextPage, results[] }</code>
|
||||||
|
<br><br>
|
||||||
|
<a href="/api/health">Health</a> · <a href="/openapi.json">OpenAPI Spec</a> · <a href="https://github.com/Shineii86/MiruroAPI" target="_blank">GitHub</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -0,0 +1,247 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.0.3",
|
||||||
|
"info": {
|
||||||
|
"title": "MiruroAPI",
|
||||||
|
"description": "Free RESTful API for anime streaming data. Powered by AniList GraphQL and Miruro streaming providers.",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"contact": {
|
||||||
|
"name": "Shineii86",
|
||||||
|
"url": "https://github.com/Shineii86/MiruroAPI"
|
||||||
|
},
|
||||||
|
"license": {
|
||||||
|
"name": "MIT"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "https://miruroapi-zeta.vercel.app",
|
||||||
|
"description": "Production"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/api/health": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["System"],
|
||||||
|
"summary": "Health check",
|
||||||
|
"operationId": "health",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "API is healthy",
|
||||||
|
"content": { "application/json": { "schema": { "type": "object" } } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/search": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["Search & Discovery"],
|
||||||
|
"summary": "Search anime by name",
|
||||||
|
"operationId": "searchAnime",
|
||||||
|
"parameters": [
|
||||||
|
{ "name": "query", "in": "query", "required": true, "schema": { "type": "string" }, "description": "Search query" },
|
||||||
|
{ "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
|
||||||
|
{ "name": "per_page", "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 50 } }
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": { "description": "Search results with pagination" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/suggestions": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["Search & Discovery"],
|
||||||
|
"summary": "Autocomplete suggestions",
|
||||||
|
"operationId": "getSuggestions",
|
||||||
|
"parameters": [
|
||||||
|
{ "name": "query", "in": "query", "required": true, "schema": { "type": "string" } }
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": { "description": "Lightweight suggestions (max 8)" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/filter": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["Search & Discovery"],
|
||||||
|
"summary": "Advanced filter / browse",
|
||||||
|
"operationId": "filterAnime",
|
||||||
|
"parameters": [
|
||||||
|
{ "name": "genre", "in": "query", "schema": { "type": "string" }, "description": "e.g. Action, Romance" },
|
||||||
|
{ "name": "tag", "in": "query", "schema": { "type": "string" }, "description": "e.g. Isekai, Time Skip" },
|
||||||
|
{ "name": "year", "in": "query", "schema": { "type": "integer" } },
|
||||||
|
{ "name": "season", "in": "query", "schema": { "type": "string", "enum": ["WINTER", "SPRING", "SUMMER", "FALL"] } },
|
||||||
|
{ "name": "format", "in": "query", "schema": { "type": "string", "enum": ["TV", "MOVIE", "OVA", "ONA", "SPECIAL", "MUSIC"] } },
|
||||||
|
{ "name": "status", "in": "query", "schema": { "type": "string", "enum": ["RELEASING", "FINISHED", "NOT_YET_RELEASED", "CANCELLED", "HIATUS"] } },
|
||||||
|
{ "name": "sort", "in": "query", "schema": { "type": "string", "enum": ["SCORE_DESC", "POPULARITY_DESC", "TRENDING_DESC", "START_DATE_DESC", "FAVOURITES_DESC", "UPDATED_AT_DESC"], "default": "POPULARITY_DESC" } },
|
||||||
|
{ "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
|
||||||
|
{ "name": "per_page", "in": "query", "schema": { "type": "integer", "default": 20, "maximum": 50 } }
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": { "description": "Filtered results with pagination" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/trending": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["Collections"],
|
||||||
|
"summary": "Trending anime",
|
||||||
|
"operationId": "getTrending",
|
||||||
|
"parameters": [
|
||||||
|
{ "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
|
||||||
|
{ "name": "per_page", "in": "query", "schema": { "type": "integer", "default": 20 } }
|
||||||
|
],
|
||||||
|
"responses": { "200": { "description": "Trending anime list" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/popular": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["Collections"],
|
||||||
|
"summary": "Most popular anime",
|
||||||
|
"operationId": "getPopular",
|
||||||
|
"parameters": [
|
||||||
|
{ "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
|
||||||
|
{ "name": "per_page", "in": "query", "schema": { "type": "integer", "default": 20 } }
|
||||||
|
],
|
||||||
|
"responses": { "200": { "description": "Popular anime list" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/upcoming": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["Collections"],
|
||||||
|
"summary": "Upcoming anime",
|
||||||
|
"operationId": "getUpcoming",
|
||||||
|
"parameters": [
|
||||||
|
{ "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
|
||||||
|
{ "name": "per_page", "in": "query", "schema": { "type": "integer", "default": 20 } }
|
||||||
|
],
|
||||||
|
"responses": { "200": { "description": "Upcoming anime list" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/recent": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["Collections"],
|
||||||
|
"summary": "Recently updated anime",
|
||||||
|
"operationId": "getRecent",
|
||||||
|
"parameters": [
|
||||||
|
{ "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
|
||||||
|
{ "name": "per_page", "in": "query", "schema": { "type": "integer", "default": 20 } }
|
||||||
|
],
|
||||||
|
"responses": { "200": { "description": "Recent anime list" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/spotlight": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["Collections"],
|
||||||
|
"summary": "Spotlight / featured anime",
|
||||||
|
"operationId": "getSpotlight",
|
||||||
|
"responses": { "200": { "description": "Top 10 trending + popular" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/schedule": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["Collections"],
|
||||||
|
"summary": "Airing schedule",
|
||||||
|
"operationId": "getSchedule",
|
||||||
|
"parameters": [
|
||||||
|
{ "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
|
||||||
|
{ "name": "per_page", "in": "query", "schema": { "type": "integer", "default": 20 } }
|
||||||
|
],
|
||||||
|
"responses": { "200": { "description": "Upcoming episodes with timestamps" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/info/{id}": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["Anime Details"],
|
||||||
|
"summary": "Full anime info",
|
||||||
|
"operationId": "getAnimeInfo",
|
||||||
|
"parameters": [
|
||||||
|
{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "AniList anime ID" }
|
||||||
|
],
|
||||||
|
"responses": { "200": { "description": "Complete anime metadata, characters, relations, recommendations" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/anime/{id}/characters": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["Anime Details"],
|
||||||
|
"summary": "Characters with voice actors",
|
||||||
|
"operationId": "getCharacters",
|
||||||
|
"parameters": [
|
||||||
|
{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } },
|
||||||
|
{ "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
|
||||||
|
{ "name": "per_page", "in": "query", "schema": { "type": "integer", "default": 25 } }
|
||||||
|
],
|
||||||
|
"responses": { "200": { "description": "Paginated character list" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/anime/{id}/relations": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["Anime Details"],
|
||||||
|
"summary": "Related anime",
|
||||||
|
"operationId": "getRelations",
|
||||||
|
"parameters": [
|
||||||
|
{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }
|
||||||
|
],
|
||||||
|
"responses": { "200": { "description": "Sequels, prequels, side stories, etc." } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/anime/{id}/recommendations": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["Anime Details"],
|
||||||
|
"summary": "Community recommendations",
|
||||||
|
"operationId": "getRecommendations",
|
||||||
|
"parameters": [
|
||||||
|
{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } },
|
||||||
|
{ "name": "page", "in": "query", "schema": { "type": "integer", "default": 1 } },
|
||||||
|
{ "name": "per_page", "in": "query", "schema": { "type": "integer", "default": 10 } }
|
||||||
|
],
|
||||||
|
"responses": { "200": { "description": "Recommended anime sorted by rating" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/episodes/{id}": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["Streaming"],
|
||||||
|
"summary": "Episode list from all providers",
|
||||||
|
"operationId": "getEpisodes",
|
||||||
|
"parameters": [
|
||||||
|
{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "AniList anime ID" }
|
||||||
|
],
|
||||||
|
"responses": { "200": { "description": "Episodes organized by provider and audio type (sub/dub)" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/sources": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["Streaming"],
|
||||||
|
"summary": "Streaming sources (detailed)",
|
||||||
|
"operationId": "getSources",
|
||||||
|
"parameters": [
|
||||||
|
{ "name": "episodeId", "in": "query", "required": true, "schema": { "type": "string" } },
|
||||||
|
{ "name": "provider", "in": "query", "required": true, "schema": { "type": "string" } },
|
||||||
|
{ "name": "anilistId", "in": "query", "required": true, "schema": { "type": "integer" } },
|
||||||
|
{ "name": "category", "in": "query", "schema": { "type": "string", "enum": ["sub", "dub"], "default": "sub" } }
|
||||||
|
],
|
||||||
|
"responses": { "200": { "description": "M3U8 streams, subtitles, intro/outro timestamps" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/watch/{provider}/{anilistId}/{category}/{slug}": {
|
||||||
|
"get": {
|
||||||
|
"tags": ["Streaming"],
|
||||||
|
"summary": "Streaming sources (simple slug-based)",
|
||||||
|
"operationId": "getWatchSources",
|
||||||
|
"parameters": [
|
||||||
|
{ "name": "provider", "in": "path", "required": true, "schema": { "type": "string" } },
|
||||||
|
{ "name": "anilistId", "in": "path", "required": true, "schema": { "type": "integer" } },
|
||||||
|
{ "name": "category", "in": "path", "required": true, "schema": { "type": "string", "enum": ["sub", "dub"] } },
|
||||||
|
{ "name": "slug", "in": "path", "required": true, "schema": { "type": "string" }, "description": "e.g. animepahe-1" }
|
||||||
|
],
|
||||||
|
"responses": { "200": { "description": "M3U8 streams, subtitles, intro/outro timestamps" } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
{ "name": "System", "description": "Health & status" },
|
||||||
|
{ "name": "Search & Discovery", "description": "Search, suggestions, and advanced filtering" },
|
||||||
|
{ "name": "Collections", "description": "Trending, popular, upcoming, recent, spotlight, schedule" },
|
||||||
|
{ "name": "Anime Details", "description": "Full anime info, characters, relations, recommendations" },
|
||||||
|
{ "name": "Streaming", "description": "Episode lists and M3U8 streaming sources" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -43,12 +43,43 @@ app.use(express.static(path.join(__dirname, "public")));
|
|||||||
app.get("/api/health", (req, res) => {
|
app.get("/api/health", (req, res) => {
|
||||||
res.json({
|
res.json({
|
||||||
status: "ok",
|
status: "ok",
|
||||||
version: "1.0.0",
|
version: "1.1.0",
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
uptime: process.uptime(),
|
uptime: process.uptime(),
|
||||||
|
endpoints: 16,
|
||||||
|
providers: ["kiwi", "pewe", "bee", "bonk", "bun", "ally", "nun", "twin", "cog", "moo", "hop", "telli"],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Swagger UI page
|
||||||
|
app.get("/docs", (req, res) => {
|
||||||
|
res.send(`<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>MiruroAPI — Swagger UI</title>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
|
||||||
|
<style>body{margin:0;background:#0b1622;}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="swagger-ui"></div>
|
||||||
|
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
|
||||||
|
<script>
|
||||||
|
SwaggerUIBundle({
|
||||||
|
url: "/openapi.json",
|
||||||
|
dom_id: "#swagger-ui",
|
||||||
|
presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset],
|
||||||
|
layout: "BaseLayout",
|
||||||
|
deepLinking: true,
|
||||||
|
defaultModelsExpandDepth: -1,
|
||||||
|
docExpansion: "list",
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>`);
|
||||||
|
});
|
||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
const apiRoutes = require("./src/routes/apiRoutes");
|
const apiRoutes = require("./src/routes/apiRoutes");
|
||||||
app.use("/api", apiRoutes);
|
app.use("/api", apiRoutes);
|
||||||
|
|||||||
+8
-1
@@ -92,7 +92,14 @@ async function fetchRawEpisodes(anilistId) {
|
|||||||
|
|
||||||
async function getEpisodes(anilistId) {
|
async function getEpisodes(anilistId) {
|
||||||
const data = await fetchRawEpisodes(anilistId);
|
const data = await fetchRawEpisodes(anilistId);
|
||||||
return injectSourceSlugs(data, anilistId);
|
const result = injectSourceSlugs(data, anilistId);
|
||||||
|
// Ensure mappings field is present (like Walter's API)
|
||||||
|
if (!result.mappings) {
|
||||||
|
result.mappings = { anilistId };
|
||||||
|
if (result.malId) result.mappings.malId = result.malId;
|
||||||
|
if (result.kitsuId) result.mappings.kitsuId = result.kitsuId;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getSources(episodeId, provider, anilistId, category = "sub") {
|
async function getSources(episodeId, provider, anilistId, category = "sub") {
|
||||||
|
|||||||
Reference in New Issue
Block a user