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:
Sʜɪɴᴇɪ Nᴏᴜᴢᴇɴ
2026-06-09 16:21:30 +05:30
parent b73d8d09bf
commit 1433afdca1
20 changed files with 2500 additions and 513 deletions
+190
View File
@@ -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 |
+375
View File
@@ -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"
}
}
```
+267
View File
@@ -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');
```
+91
View File
@@ -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.
+238
View File
@@ -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 |